Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce76aa23a6 | ||
|
|
94b635daf8 | ||
|
|
31871aaa4b | ||
|
|
9d465663db | ||
|
|
70081f2647 | ||
|
|
fa9d2f266f | ||
|
|
8fcfb502ca | ||
|
|
0bab2aba9e | ||
|
|
38951d950f | ||
|
|
dda8fdbcb2 | ||
|
|
bf3ba65782 | ||
|
|
f35b7c9d9d | ||
|
|
e9506c3eae | ||
|
|
bb4465548b | ||
|
|
cf8b5ca768 | ||
|
|
f93d760b1b | ||
|
|
1495ee8dd8 | ||
|
|
8a7279e3ee | ||
|
|
fdff7a38f4 | ||
|
|
f26a68587d | ||
|
|
1c62038344 | ||
|
|
6eb7b4b8ec | ||
|
|
f11fc37409 | ||
|
|
d12a3dc8a8 | ||
|
|
0cfab8770a | ||
|
|
3876ae8fe8 | ||
|
|
77644e4425 | ||
|
|
6592782dc0 | ||
|
|
d6dc250ed4 | ||
|
|
9579f14c34 | ||
|
|
c5acbffe3f | ||
|
|
63142042bc | ||
|
|
1ac0305ed0 | ||
|
|
7860013aa9 | ||
|
|
7a5b964611 | ||
|
|
6950c2e4d2 | ||
|
|
291223b3ce | ||
|
|
99aeb766c3 | ||
|
|
93fe31cc55 | ||
|
|
b9a03fabd9 | ||
|
|
d00b3ea8f8 | ||
|
|
c18afcddc4 | ||
|
|
57db25d08a | ||
|
|
b8f64a1c1b | ||
|
|
de35dee1c5 | ||
|
|
dd883985bb | ||
|
|
97b8911ba8 | ||
|
|
a397e7305d | ||
|
|
d0039afbb7 | ||
|
|
878cdfbc52 | ||
|
|
1165f00bd4 | ||
|
|
949ddffef2 | ||
|
|
c4725428e0 | ||
|
|
d29ad356d1 | ||
|
|
692ca5eaf0 | ||
|
|
b9787c78f3 | ||
|
|
dec7a02737 | ||
|
|
0eade717ce | ||
|
|
e6c674b3c6 | ||
|
|
4ff247e134 | ||
|
|
2f0c1f5fa2 | ||
|
|
07692653ff | ||
|
|
82dc57ad43 | ||
|
|
84e8632b98 | ||
|
|
571ce6cb0d | ||
|
|
783503aece | ||
|
|
b482a9bf0d | ||
|
|
36c6cc203e | ||
|
|
8950585141 | ||
|
|
950028abeb | ||
|
|
280fa562a6 | ||
|
|
6b1fa87ad3 | ||
|
|
cacfb2bc08 | ||
|
|
3107ae4147 | ||
|
|
c182114883 | ||
|
|
342b239ac6 | ||
|
|
0f41aac20b | ||
|
|
cd51a59e72 | ||
|
|
c829330b53 | ||
|
|
c14cf86f83 | ||
|
|
6d620c00a1 | ||
|
|
06e8713fa5 | ||
|
|
af9b42549f | ||
|
|
75baf7ce33 | ||
|
|
4ff6347155 | ||
|
|
14ee054359 | ||
|
|
7f559ffd07 | ||
|
|
619f6837b0 | ||
|
|
d778c192ae | ||
|
|
a290c6d7db | ||
|
|
c1b0207800 | ||
|
|
c7a5c7efee | ||
|
|
cbeec6d225 | ||
|
|
25e47c3ce8 | ||
|
|
5eb3bf4058 | ||
|
|
07dfdce8e4 | ||
|
|
e5e1b6dee4 | ||
|
|
1582b8b2cd | ||
|
|
21ad653b7e |
6
.github/CODEOWNERS
vendored
@@ -1,5 +1 @@
|
|||||||
# These owners will be the default owners for everything in the repo.
|
* @filebrowser/maintainers
|
||||||
# Unless a later match takes precedence, @o1egl will be requested for
|
|
||||||
# review when someone opens a pull request.
|
|
||||||
|
|
||||||
* @o1egl @hacdias
|
|
||||||
|
|||||||
115
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-frontend:
|
||||||
|
name: Lint Frontend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
package_json_file: "frontend/package.json"
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24.x"
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
||||||
|
- working-directory: frontend
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm run lint
|
||||||
|
|
||||||
|
lint-backend:
|
||||||
|
name: Lint Backend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: "1.25.x"
|
||||||
|
- uses: golangci/golangci-lint-action@v9
|
||||||
|
with:
|
||||||
|
version: "latest"
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: "1.25.x"
|
||||||
|
- run: go test --race ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
package_json_file: "frontend/package.json"
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24.x"
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
|
- run: task build
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
needs: ["lint-frontend", "lint-backend", "test", "build"]
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
package_json_file: "frontend/package.json"
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24.x"
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
|
- run: task build-frontend
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
52
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'www'
|
||||||
|
- '*.md'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Docs
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
|
- name: Build site
|
||||||
|
run: task docs
|
||||||
|
|
||||||
|
build-and-release:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
|
name: Build and Release Docs
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
|
- name: Build site
|
||||||
|
run: task docs
|
||||||
|
- 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 }}
|
||||||
|
|
||||||
@@ -13,10 +13,10 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
name: Validate PR title
|
name: Validate Title
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v5
|
- uses: amannn/action-semantic-pull-request@v6
|
||||||
id: lint_pr_title
|
id: lint_pr_title
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -43,4 +43,4 @@ jobs:
|
|||||||
uses: marocchino/sticky-pull-request-comment@v2
|
uses: marocchino/sticky-pull-request-comment@v2
|
||||||
with:
|
with:
|
||||||
header: pr-title-lint-error
|
header: pr-title-lint-error
|
||||||
delete: true
|
delete: true
|
||||||
105
.github/workflows/main.yaml
vendored
@@ -1,105 +0,0 @@
|
|||||||
name: main
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "master"
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# linters
|
|
||||||
lint-frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: "frontend/package.json"
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22.x"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
|
||||||
- run: make lint-frontend
|
|
||||||
lint-backend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.23.0
|
|
||||||
- run: make lint-backend
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [lint-frontend, lint-backend]
|
|
||||||
steps:
|
|
||||||
- run: echo "done"
|
|
||||||
|
|
||||||
# tests
|
|
||||||
test-frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: "frontend/package.json"
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22.x"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
|
||||||
- run: make test-frontend
|
|
||||||
test-backend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.23.0
|
|
||||||
- run: make test-backend
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [test-frontend, test-backend]
|
|
||||||
steps:
|
|
||||||
- run: echo "done"
|
|
||||||
|
|
||||||
# release
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [lint, test]
|
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: 1.23.0
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: "frontend/package.json"
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22.x"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Build frontend
|
|
||||||
run: make build-frontend
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: release --clean
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
||||||
20
.github/workflows/site-pr.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
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
@@ -1,32 +0,0 @@
|
|||||||
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 }}
|
|
||||||
5
.gitignore
vendored
@@ -35,10 +35,5 @@ build/
|
|||||||
/frontend/dist/*
|
/frontend/dist/*
|
||||||
!/frontend/dist/.gitkeep
|
!/frontend/dist/.gitkeep
|
||||||
|
|
||||||
# Playwright files
|
|
||||||
/frontend/test-results/
|
|
||||||
/frontend/playwright-report/
|
|
||||||
/frontend/playwright/.cache/
|
|
||||||
|
|
||||||
default.nix
|
default.nix
|
||||||
Dockerfile.dev
|
Dockerfile.dev
|
||||||
|
|||||||
216
CHANGELOG.md
@@ -1,6 +1,220 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
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.
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.45.3](https://github.com/filebrowser/filebrowser/compare/v2.45.2...v2.45.3) (2025-11-13)
|
||||||
|
|
||||||
|
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** update module github.com/shirou/gopsutil/v3 to v4 ([#5536](https://github.com/filebrowser/filebrowser/issues/5536)) ([fdff7a3](https://github.com/filebrowser/filebrowser/commit/fdff7a38f4711f2b58dfdd60bebbb057bd3a478d))
|
||||||
|
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#5537](https://github.com/filebrowser/filebrowser/issues/5537)) ([f26a685](https://github.com/filebrowser/filebrowser/commit/f26a68587d8432b536453093f42dc255d19d10fa))
|
||||||
|
|
||||||
|
### [2.45.1](https://github.com/filebrowser/filebrowser/compare/v2.45.0...v2.45.1) (2025-11-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* share page preview items to contain baseUrl ([#5510](https://github.com/filebrowser/filebrowser/issues/5510)) ([6950c2e](https://github.com/filebrowser/filebrowser/commit/6950c2e4d2868f06235f93c0a18b303b4095ca0a))
|
||||||
|
|
||||||
|
## [2.45.0](https://github.com/filebrowser/filebrowser/compare/v2.44.2...v2.45.0) (2025-11-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* update translations ([#5458](https://github.com/filebrowser/filebrowser/issues/5458)) ([b9a03fa](https://github.com/filebrowser/filebrowser/commit/b9a03fabd98119d6588882f5ba2a7d29b012d729))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* support croatian ([#5502](https://github.com/filebrowser/filebrowser/issues/5502)) ([93fe31c](https://github.com/filebrowser/filebrowser/commit/93fe31cc55c9d9d27c634993619a768fa700da1d))
|
||||||
|
|
||||||
|
### [2.44.2](https://github.com/filebrowser/filebrowser/compare/v2.44.1...v2.44.2) (2025-10-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **http:** remove auth query parameter ([57db25d](https://github.com/filebrowser/filebrowser/commit/57db25d08a1ef2cd0b41f34e312b7b7c35c7ed38))
|
||||||
|
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
* **deps-dev:** bump vite from 6.3.6 to 6.4.1 in /frontend ([b8f64a1](https://github.com/filebrowser/filebrowser/commit/b8f64a1c1bc235df784d7f52abd3a9e84c6db6ce))
|
||||||
|
|
||||||
|
### [2.44.1](https://github.com/filebrowser/filebrowser/compare/v2.44.0...v2.44.1) (2025-10-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** prevent integer overflow in logout timer using safeTimeout ([#5470](https://github.com/filebrowser/filebrowser/issues/5470)) ([dd88398](https://github.com/filebrowser/filebrowser/commit/dd883985bb484af9dfea2677a40d56999fdc72f3))
|
||||||
|
* editor discard prompt doesn't save nor discard ([a397e73](https://github.com/filebrowser/filebrowser/commit/a397e7305d1572baf67823413f97a29eea38f0cc))
|
||||||
|
* wrong url on settings branding link ([d0039af](https://github.com/filebrowser/filebrowser/commit/d0039afbb76a9364c1e6ac9715ccc3c239dc8cb6))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorings
|
||||||
|
|
||||||
|
* use slices.Contains to simplify code ([#5483](https://github.com/filebrowser/filebrowser/issues/5483)) ([97b8911](https://github.com/filebrowser/filebrowser/commit/97b8911ba8a65456091cbec0202f6b5209fcf363))
|
||||||
|
|
||||||
|
## [2.44.0](https://github.com/filebrowser/filebrowser/compare/v2.43.0...v2.44.0) (2025-09-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* allow setting ace editor theme ([#3826](https://github.com/filebrowser/filebrowser/issues/3826)) ([b9787c7](https://github.com/filebrowser/filebrowser/commit/b9787c78f3889171f94db19e7655dce68c64b6fb))
|
||||||
|
* Improved path display in the new file and directory modal ([#5451](https://github.com/filebrowser/filebrowser/issues/5451)) ([d29ad35](https://github.com/filebrowser/filebrowser/commit/d29ad356d1067c87b2821debab91286549f512a0))
|
||||||
|
* Translate frontend/src/i18n/en.json in no ([dec7a02](https://github.com/filebrowser/filebrowser/commit/dec7a027378fbc6948d203199c44a640a141bcad))
|
||||||
|
* Updates for project File Browser ([#5446](https://github.com/filebrowser/filebrowser/issues/5446)) ([4ff247e](https://github.com/filebrowser/filebrowser/commit/4ff247e134e4d61668ee656a258ed67f71414e18))
|
||||||
|
* Updates for project File Browser ([#5450](https://github.com/filebrowser/filebrowser/issues/5450)) ([0eade71](https://github.com/filebrowser/filebrowser/commit/0eade717ce9d04bf48051922f11d983edbc7c2d0))
|
||||||
|
* Updates for project File Browser ([#5457](https://github.com/filebrowser/filebrowser/issues/5457)) ([1165f00](https://github.com/filebrowser/filebrowser/commit/1165f00bd4dcb0dcfbc084f54f51902ba4b4a714))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* computation of file path ([c472542](https://github.com/filebrowser/filebrowser/commit/c4725428e07da72b855009e2c13c6ed91d32e0b7))
|
||||||
|
* show login when session token expires ([e6c674b](https://github.com/filebrowser/filebrowser/commit/e6c674b3c616831942c4d4aacab0907d58003e23))
|
||||||
|
* some formatting issues with i18n files ([949ddff](https://github.com/filebrowser/filebrowser/commit/949ddffef20e38169902c5fd74dca4815dcecf11))
|
||||||
|
* **upload:** throttle upload speed calculation to 100ms to avoid Infinity MB/s ([#5456](https://github.com/filebrowser/filebrowser/issues/5456)) ([692ca5e](https://github.com/filebrowser/filebrowser/commit/692ca5eaf01e4dcf346ba03f82c5dbd50cce246b))
|
||||||
|
|
||||||
|
## [2.43.0](https://github.com/filebrowser/filebrowser/compare/v2.42.5...v2.43.0) (2025-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* "save changes" button to discard changes dialog ([84e8632](https://github.com/filebrowser/filebrowser/commit/84e8632b98e315bfef2da77dd7d1049daec99241))
|
||||||
|
* Translate frontend/src/i18n/en.json in es ([571ce6c](https://github.com/filebrowser/filebrowser/commit/571ce6cb0d7c8725d1cc1a3238ea506ddc72b060))
|
||||||
|
* Translate frontend/src/i18n/en.json in fr ([6b1fa87](https://github.com/filebrowser/filebrowser/commit/6b1fa87ad38ebbb1a9c5d0e5fc88ba796c148bcf))
|
||||||
|
* Updates for project File Browser ([#5427](https://github.com/filebrowser/filebrowser/issues/5427)) ([8950585](https://github.com/filebrowser/filebrowser/commit/89505851414bfcee6b9ff02087eb4cec51c330f6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* optimize markdown preview height ([783503a](https://github.com/filebrowser/filebrowser/commit/783503aece7fca9e26f7e849b0e7478aba976acb))
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* build(deps): bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 ([0769265](https://github.com/filebrowser/filebrowser/commit/07692653ffe0ea5e517e6dc1fd3961172e931843))
|
||||||
|
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
* **deps-dev:** bump vite from 6.1.6 to 6.3.6 in /frontend ([36c6cc2](https://github.com/filebrowser/filebrowser/commit/36c6cc203e10947439519a0413d5817921a1690d))
|
||||||
|
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([280fa56](https://github.com/filebrowser/filebrowser/commit/280fa562a67824887ae6e2530a3b73739d6e1bb4))
|
||||||
|
* **deps:** bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 ([950028a](https://github.com/filebrowser/filebrowser/commit/950028abebe2898bac4ecfd8715c0967246310cb))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorings
|
||||||
|
|
||||||
|
* to use strings.Lines ([b482a9b](https://github.com/filebrowser/filebrowser/commit/b482a9bf0d292ec6542d2145a4408971e4c985f1))
|
||||||
|
|
||||||
|
## [2.43.0](https://github.com/filebrowser/filebrowser/compare/v2.42.5...v2.43.0) (2025-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* "save changes" button to discard changes dialog ([84e8632](https://github.com/filebrowser/filebrowser/commit/84e8632b98e315bfef2da77dd7d1049daec99241))
|
||||||
|
* Translate frontend/src/i18n/en.json in es ([571ce6c](https://github.com/filebrowser/filebrowser/commit/571ce6cb0d7c8725d1cc1a3238ea506ddc72b060))
|
||||||
|
* Translate frontend/src/i18n/en.json in fr ([6b1fa87](https://github.com/filebrowser/filebrowser/commit/6b1fa87ad38ebbb1a9c5d0e5fc88ba796c148bcf))
|
||||||
|
* Updates for project File Browser ([#5427](https://github.com/filebrowser/filebrowser/issues/5427)) ([8950585](https://github.com/filebrowser/filebrowser/commit/89505851414bfcee6b9ff02087eb4cec51c330f6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* optimize markdown preview height ([783503a](https://github.com/filebrowser/filebrowser/commit/783503aece7fca9e26f7e849b0e7478aba976acb))
|
||||||
|
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
* **deps-dev:** bump vite from 6.1.6 to 6.3.6 in /frontend ([36c6cc2](https://github.com/filebrowser/filebrowser/commit/36c6cc203e10947439519a0413d5817921a1690d))
|
||||||
|
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([280fa56](https://github.com/filebrowser/filebrowser/commit/280fa562a67824887ae6e2530a3b73739d6e1bb4))
|
||||||
|
* **deps:** bump github.com/ulikunitz/xz from 0.5.12 to 0.5.14 ([950028a](https://github.com/filebrowser/filebrowser/commit/950028abebe2898bac4ecfd8715c0967246310cb))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorings
|
||||||
|
|
||||||
|
* to use strings.Lines ([b482a9b](https://github.com/filebrowser/filebrowser/commit/b482a9bf0d292ec6542d2145a4408971e4c985f1))
|
||||||
|
|
||||||
|
### [2.42.5](https://github.com/filebrowser/filebrowser/compare/v2.42.4...v2.42.5) (2025-08-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* "new folder" button not working in the move and copy popup ([#5368](https://github.com/filebrowser/filebrowser/issues/5368)) ([3107ae4](https://github.com/filebrowser/filebrowser/commit/3107ae41475ae9383c3af414d25a133e549f8087))
|
||||||
|
|
||||||
|
### [2.42.4](https://github.com/filebrowser/filebrowser/compare/v2.42.3...v2.42.4) (2025-08-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add libcap to Dockerfile.s6 ([342b239](https://github.com/filebrowser/filebrowser/commit/342b239ac6f4af2453d5f7aa27f7f0093024dd72))
|
||||||
|
|
||||||
|
### [2.42.3](https://github.com/filebrowser/filebrowser/compare/v2.42.2...v2.42.3) (2025-08-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add missing CLI flags for user management ([#5351](https://github.com/filebrowser/filebrowser/issues/5351)) ([cd51a59](https://github.com/filebrowser/filebrowser/commit/cd51a59e72c72560fce7bcc9b12aaf02646b699c))
|
||||||
|
|
||||||
|
### [2.42.2](https://github.com/filebrowser/filebrowser/compare/v2.42.1...v2.42.2) (2025-08-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* show file upload errors ([06e8713](https://github.com/filebrowser/filebrowser/commit/06e8713fa55065d38f02499d3e8d39fc86926cab))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorings
|
||||||
|
|
||||||
|
* upload progress calculation ([#5350](https://github.com/filebrowser/filebrowser/issues/5350)) ([c14cf86](https://github.com/filebrowser/filebrowser/commit/c14cf86f8304e01d804e01a7eef5ea093627ef37))
|
||||||
|
|
||||||
|
### [2.42.1](https://github.com/filebrowser/filebrowser/compare/v2.42.0...v2.42.1) (2025-07-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Translate frontend/src/i18n/en.json in sk ([14ee054](https://github.com/filebrowser/filebrowser/commit/14ee0543599f2ec73b7f5d2dbd8415f47fe592aa))
|
||||||
|
* Translate frontend/src/i18n/en.json in vi ([75baf7c](https://github.com/filebrowser/filebrowser/commit/75baf7ce337671a1045f897ba4a19967a31b1aec))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* directory mode on config init ([4ff6347](https://github.com/filebrowser/filebrowser/commit/4ff634715543b65878943273dff70f340167900b))
|
||||||
|
|
||||||
|
## [2.42.0](https://github.com/filebrowser/filebrowser/compare/v2.41.0...v2.42.0) (2025-07-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add Norwegian support ([#5332](https://github.com/filebrowser/filebrowser/issues/5332)) ([25e47c3](https://github.com/filebrowser/filebrowser/commit/25e47c3ce8b35b820b5370a4b8bfdf682bd5ae0b))
|
||||||
|
* select item on file list after navigating back ([#5329](https://github.com/filebrowser/filebrowser/issues/5329)) ([cbeec6d](https://github.com/filebrowser/filebrowser/commit/cbeec6d225691723c4750d7f84122ebb14d662bf))
|
||||||
|
* Translate frontend/src/i18n/en.json in no ([5eb3bf4](https://github.com/filebrowser/filebrowser/commit/5eb3bf40586c2ffc32f4834b5dd59f0eb719c1f7))
|
||||||
|
* Translate frontend/src/i18n/en.json in sk ([07dfdce](https://github.com/filebrowser/filebrowser/commit/07dfdce8e4c371f4ca7480f3cef0bd66ff5c9abb))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* norsk loading ([619f683](https://github.com/filebrowser/filebrowser/commit/619f6837b0d1ec6c654d30f4ecedd6696874721f))
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* Revert "chore(release): 2.42.0" ([d778c19](https://github.com/filebrowser/filebrowser/commit/d778c192ae02c5e73781f7632e3b7276c5811e17))
|
||||||
|
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
* bump go version to 1.23.11 ([c7a5c7e](https://github.com/filebrowser/filebrowser/commit/c7a5c7efee2b2bede89ec90bafd1af61c39519ff))
|
||||||
|
* bump to go 1.24 ([c1b0207](https://github.com/filebrowser/filebrowser/commit/c1b0207800b4bb52c8dd459c1d69ce0f785473b6))
|
||||||
|
|
||||||
|
## [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)
|
### [2.40.2](https://github.com/filebrowser/filebrowser/compare/v2.40.1...v2.40.2) (2025-07-17)
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,23 @@ We encourage you to use git to manage your fork. To clone the main repository, j
|
|||||||
git clone https://github.com/filebrowser/filebrowser
|
git clone https://github.com/filebrowser/filebrowser
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We use [Taskfile](https://taskfile.dev/) to manage the different processes (building, releasing, etc) automatically.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
You can fully build the project in order to produce a binary by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
For development, there are a few things to have in mind.
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
We are using [Node.js](https://nodejs.org/en/) on the frontend to manage the build process. The steps to build it are:
|
We use [Node.js](https://nodejs.org/en/) on the frontend to manage the build process. Prepare the frontend environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From the root of the repo, go to frontend/
|
# From the root of the repo, go to frontend/
|
||||||
@@ -27,37 +39,62 @@ cd frontend
|
|||||||
|
|
||||||
# Install the dependencies
|
# Install the dependencies
|
||||||
pnpm install
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
# Build the frontend
|
If you just want to develop the backend, you can create a static build of the frontend:
|
||||||
|
|
||||||
|
```bash
|
||||||
pnpm run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
This will install the dependencies and build the frontend so you can then embed it into the Go app. Although, if you want to play with it, you'll get bored of building it after every change you do. So, you can run the command below to watch for changes:
|
If you want to develop the frontend, start a development server which watches for changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Please note that you need to access File Browser's interface through the development server of the frontend.
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
First of all, you need to download the required dependencies. We are using the built-in `go mod` tool for dependency management. To get the modules, run:
|
First prepare the backend environment by downloading all required dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go mod download
|
go mod download
|
||||||
```
|
```
|
||||||
|
|
||||||
The magic of File Browser is that the static assets are bundled into the final binary. For that, we use [Go embed.FS](https://golang.org/pkg/embed/). The files from `frontend/dist` will be embedded during the build process.
|
You can now build or run File Browser as any other Go project:
|
||||||
|
|
||||||
To build File Browser is just like any other Go program:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Build
|
||||||
go build
|
go build
|
||||||
|
|
||||||
|
# Run
|
||||||
|
go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
To create a development build use the "dev" tag, this way the content inside the frontend folder will not be embedded in the binary but will be reloaded at every change:
|
## Documentation
|
||||||
|
|
||||||
|
We rely on Docker to abstract all the dependencies required for building the documentation.
|
||||||
|
|
||||||
|
To build the documentation to `www/public`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build -tags dev
|
task docs
|
||||||
|
```
|
||||||
|
|
||||||
|
To start a local server on port `8000` to view the built documentation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task docs-serve
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
To make a release, just run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task release
|
||||||
```
|
```
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk --no-cache add ca-certificates mailcap jq
|
apk --no-cache add ca-certificates mailcap jq libcap
|
||||||
|
|
||||||
# Make user and create necessary directories
|
# Make user and create necessary directories
|
||||||
RUN mkdir -p /config /database /srv && \
|
RUN mkdir -p /config /database /srv && \
|
||||||
@@ -12,7 +12,8 @@ COPY filebrowser /bin/filebrowser
|
|||||||
COPY docker/common/ /
|
COPY docker/common/ /
|
||||||
COPY docker/s6/ /
|
COPY docker/s6/ /
|
||||||
|
|
||||||
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh
|
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh && \
|
||||||
|
setcap 'cap_net_bind_service=+ep' /bin/filebrowser
|
||||||
|
|
||||||
# Define healthcheck script
|
# Define healthcheck script
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
||||||
|
|||||||
88
Makefile
@@ -1,88 +0,0 @@
|
|||||||
include common.mk
|
|
||||||
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
|
|
||||||
build: | build-frontend build-backend ## Build binary
|
|
||||||
|
|
||||||
.PHONY: build-frontend
|
|
||||||
build-frontend: ## Build frontend
|
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run build
|
|
||||||
|
|
||||||
.PHONY: build-backend
|
|
||||||
build-backend: ## Build backend
|
|
||||||
$Q $(go) build -ldflags '$(LDFLAGS)' -o .
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: | test-frontend test-backend ## Run all tests
|
|
||||||
|
|
||||||
.PHONY: test-frontend
|
|
||||||
test-frontend: ## Run frontend tests
|
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run typecheck
|
|
||||||
|
|
||||||
.PHONY: test-backend
|
|
||||||
test-backend: ## Run backend tests
|
|
||||||
$Q $(go) test -v ./...
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: lint-frontend lint-backend ## Run all linters
|
|
||||||
|
|
||||||
.PHONY: lint-frontend
|
|
||||||
lint-frontend: ## Run frontend linters
|
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run lint
|
|
||||||
|
|
||||||
.PHONY: lint-backend
|
|
||||||
lint-backend: | $(golangci-lint) ## Run backend linters
|
|
||||||
$Q $(golangci-lint) run -v
|
|
||||||
|
|
||||||
.PHONY: lint-commits
|
|
||||||
lint-commits: $(commitlint) ## Run commit linters
|
|
||||||
$Q ./scripts/commitlint.sh
|
|
||||||
|
|
||||||
fmt: $(goimports) ## Format source files
|
|
||||||
$Q $(goimports) -local $(MODULE) -w $$(find . -type f -name '*.go' -not -path "./vendor/*")
|
|
||||||
|
|
||||||
clean: clean-tools ## Clean
|
|
||||||
|
|
||||||
## Release:
|
|
||||||
|
|
||||||
.PHONY: bump-version
|
|
||||||
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 ''
|
|
||||||
@echo 'Usage:'
|
|
||||||
@echo ' ${YELLOW}make${RESET} ${GREEN}<target> [options]${RESET}'
|
|
||||||
@echo ''
|
|
||||||
@echo 'Options:'
|
|
||||||
@$(call global_option, "V [0|1]", "enable verbose mode (default:0)")
|
|
||||||
@echo ''
|
|
||||||
@echo 'Targets:'
|
|
||||||
@awk 'BEGIN {FS = ":.*?## "} { \
|
|
||||||
if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
|
|
||||||
else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \
|
|
||||||
}' $(MAKEFILE_LIST)
|
|
||||||
22
README.md
@@ -1,12 +1,10 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
|
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
||||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
|
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
|
||||||
[](http://godoc.org/github.com/filebrowser/filebrowser)
|
|
||||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||||
[](http://webchat.freenode.net/?channels=%23filebrowser)
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -16,18 +14,12 @@ Documentation on how to install, configure, and contribute to this project is ho
|
|||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
> [!WARNING]
|
This project is a finished product which fulfills its goal: be a single binary web File Browser which can be run by anyone anywhere. That means that File Browser is currently on **maintenance-only** mode. Therefore, please note the following:
|
||||||
>
|
|
||||||
> This project is currently on **maintenance-only** mode, and is looking for new maintainers. For more information, please read the [discussion #4906](https://github.com/filebrowser/filebrowser/discussions/4906). Therefore, please note the following:
|
|
||||||
>
|
|
||||||
> - It can take a while until someone gets back to you. Please be patient.
|
|
||||||
> - [Issues][issues] are only being used to track bugs. Any unrelated issues will be converted into a [discussion][discussions].
|
|
||||||
> - No new features will be implemented until further notice. The priority is on triaging issues and merge bug fixes.
|
|
||||||
>
|
|
||||||
> If you're interested in maintaining this project, please reach out via the discussion above.
|
|
||||||
|
|
||||||
[issues]: https://github.com/filebrowser/filebrowser/issues
|
- It can take a while until someone gets back to you. Please be patient.
|
||||||
[discussions]: https://github.com/filebrowser/filebrowser/discussions
|
- [Issues](https://github.com/filebrowser/filebrowser/issues) are meant to track bugs. Unrelated issues will be converted into [discussions](https://github.com/filebrowser/filebrowser/discussions).
|
||||||
|
- No new features will be implemented by maintainers. Pull requests for new features will be reviewed on a case by case basis.
|
||||||
|
- The priority is triaging issues, addressing security issues, and fixing bug fixes.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
69
Taskfile.yml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
SITE_DOCKER_FLAGS: >-
|
||||||
|
-v ./www:/docs
|
||||||
|
-v ./LICENSE:/docs/docs/LICENSE
|
||||||
|
-v ./SECURITY.md:/docs/docs/security.md
|
||||||
|
-v ./CHANGELOG.md:/docs/docs/changelog.md
|
||||||
|
-v ./CODE-OF-CONDUCT.md:/docs/docs/code-of-conduct.md
|
||||||
|
-v ./CONTRIBUTING.md:/docs/docs/contributing.md
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build-frontend:
|
||||||
|
desc: Build frontend assets
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- pnpm install --frozen-lockfile
|
||||||
|
- pnpm run build
|
||||||
|
|
||||||
|
build-backend:
|
||||||
|
desc: Build backend binary
|
||||||
|
cmds:
|
||||||
|
- go build -ldflags='-s -w -X "github.com/filebrowser/filebrowser/v2/version.Version={{.VERSION}}" -X "github.com/filebrowser/filebrowser/v2/version.CommitSHA={{.GIT_COMMIT}}"' -o filebrowser .
|
||||||
|
vars:
|
||||||
|
GIT_COMMIT:
|
||||||
|
sh: git log -n 1 --format=%h
|
||||||
|
VERSION:
|
||||||
|
sh: git describe --tags --abbrev=0 --match=v* | cut -c 2-
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: Build both frontend and backend
|
||||||
|
cmds:
|
||||||
|
- task: build-frontend
|
||||||
|
- task: build-backend
|
||||||
|
|
||||||
|
release-make:
|
||||||
|
internal: true
|
||||||
|
prompt: Do you wish to proceed?
|
||||||
|
cmds:
|
||||||
|
- pnpm dlx commit-and-tag-version -s
|
||||||
|
|
||||||
|
release-dry-run:
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- pnpm dlx commit-and-tag-version --dry-run --skip
|
||||||
|
|
||||||
|
release:
|
||||||
|
desc: Create a new release
|
||||||
|
cmds:
|
||||||
|
- task: release-dry-run
|
||||||
|
- task: release-make
|
||||||
|
|
||||||
|
docs-image-make:
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
||||||
|
|
||||||
|
docs:
|
||||||
|
desc: Generate documentation
|
||||||
|
cmds:
|
||||||
|
- rm -rf www/public
|
||||||
|
- task: docs-image-make
|
||||||
|
- docker run --rm {{.SITE_DOCKER_FLAGS}} filebrowser.site build -d "public"
|
||||||
|
|
||||||
|
docs-serve:
|
||||||
|
desc: Serve documentation
|
||||||
|
cmds:
|
||||||
|
- task: docs-image-make
|
||||||
|
- docker run --rm -it -p 8000:8000 {{.SITE_DOCKER_FLAGS}} filebrowser.site
|
||||||
11
auth/hook.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||||
@@ -123,7 +124,7 @@ func (a *HookAuth) GetValues(s string) {
|
|||||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||||
|
|
||||||
// iterate input lines
|
// iterate input lines
|
||||||
for _, val := range strings.Split(s, "\n") {
|
for val := range strings.Lines(s) {
|
||||||
v := strings.SplitN(val, "=", 2)
|
v := strings.SplitN(val, "=", 2)
|
||||||
|
|
||||||
// skips non key and value format
|
// skips non key and value format
|
||||||
@@ -266,13 +267,7 @@ var validHookFields = []string{
|
|||||||
|
|
||||||
// IsValid checks if the provided field is on the valid fields list
|
// IsValid checks if the provided field is on the valid fields list
|
||||||
func (hf *hookFields) IsValid(field string) bool {
|
func (hf *hookFields) IsValid(field string) bool {
|
||||||
for _, val := range validHookFields {
|
return slices.Contains(validHookFields, field)
|
||||||
if field == val {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString returns the string value or provided default
|
// GetString returns the string value or provided default
|
||||||
|
|||||||
BIN
branding/banner.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
1
branding/banner.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
branding/icon.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
1
branding/icon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="700" height="700" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"><defs><style>.prefix__fil1{fill:#fefefe}.prefix__fil6{fill:#006498}.prefix__fil5{fill:#bdeaff}</style></defs><g id="prefix__Layer_x0020_1"><path d="M80 0h540c44 0 80 36 80 80v540c0 44-36 80-80 80H80c-44 0-80-36-80-80V80C0 36 36 0 80 0z" fill="#455a64"/><path class="prefix__fil1" d="M350 71c154 0 279 125 279 279S504 629 350 629 71 504 71 350 196 71 350 71z"/><path d="M475 236l118 151c3 116-149 252-292 198l-76-99 114-156s138-95 136-94z" fill="#332c2b" fill-opacity=".149"/><path d="M231 211h208l38 24v246c0 5-3 8-8 8H231c-5 0-8-3-8-8V219c0-5 3-8 8-8z" fill="#2bbcff"/><path d="M231 211h208l38 24v2l-37-23H231c-4 0-7 3-7 7v263c-1-1-1-2-1-3V219c0-5 3-8 8-8z" fill="#53c6fc"/><path class="prefix__fil5" d="M305 212h113v98H305zM255 363h189c3 0 5 2 5 4v116H250V367c0-2 2-4 5-4z"/><path class="prefix__fil6" d="M250 470h199v13H250zM380 226h10c3 0 6 2 6 5v40c0 3-3 6-6 6h-10c-3 0-6-3-6-6v-40c0-3 3-5 6-5z"/><path class="prefix__fil1" d="M254 226c10 0 17 7 17 17 0 9-7 16-17 16-9 0-17-7-17-16 0-10 8-17 17-17z"/><path class="prefix__fil6" d="M267 448h165c2 0 3 1 3 3 0 1-1 3-3 3H267c-2 0-3-2-3-3 0-2 1-3 3-3zM267 415h165c2 0 3 1 3 3 0 1-1 2-3 2H267c-2 0-3-1-3-2 0-2 1-3 3-3zM267 381h165c2 0 3 2 3 3 0 2-1 3-3 3H267c-2 0-3-1-3-3 0-1 1-3 3-3z"/><path class="prefix__fil1" d="M236 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5zM463 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z"/><path class="prefix__fil6" d="M305 212h-21v98h21z"/><path d="M477 479v2c0 5-3 8-8 8H231c-5 0-8-3-8-8v-2c0 4 3 8 8 8h238c5 0 8-4 8-8z" fill="#0ea5eb"/><path d="M350 70c155 0 280 125 280 280S505 630 350 630 70 505 70 350 195 70 350 70zm0 46c129 0 234 105 234 234S479 584 350 584 116 479 116 350s105-234 234-234z" fill="#2979ff"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
BIN
branding/logo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
1
branding/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="560" height="560" version="1.1" id="prefix__svg44" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision"><defs id="prefix__defs4"><style type="text/css" id="style2">.prefix__fil1{fill:#fefefe}.prefix__fil6{fill:#006498}.prefix__fil5{fill:#bdeaff}</style></defs><g id="prefix__g85" transform="translate(-70 -70)"><path class="prefix__fil1" d="M350 71c154 0 279 125 279 279S504 629 350 629 71 504 71 350 196 71 350 71z" id="prefix__path9" fill="#fefefe"/><path d="M475 236l118 151c3 116-149 252-292 198l-76-99 114-156s138-95 136-94z" id="prefix__path11" fill="#332c2b" fill-opacity=".149"/><path d="M231 211h208l38 24v246c0 5-3 8-8 8H231c-5 0-8-3-8-8V219c0-5 3-8 8-8z" id="prefix__path13" fill="#2bbcff"/><path d="M231 211h208l38 24v2l-37-23H231c-4 0-7 3-7 7v263c-1-1-1-2-1-3V219c0-5 3-8 8-8z" id="prefix__path15" fill="#53c6fc"/><path class="prefix__fil5" id="prefix__polygon17" fill="#bdeaff" d="M305 212h113v98H305z"/><path class="prefix__fil5" d="M255 363h189c3 0 5 2 5 4v116H250V367c0-2 2-4 5-4z" id="prefix__path19" fill="#bdeaff"/><path class="prefix__fil6" id="prefix__polygon21" fill="#006498" d="M250 470h199v13H250z"/><path class="prefix__fil6" d="M380 226h10c3 0 6 2 6 5v40c0 3-3 6-6 6h-10c-3 0-6-3-6-6v-40c0-3 3-5 6-5z" id="prefix__path23" fill="#006498"/><path class="prefix__fil1" d="M254 226c10 0 17 7 17 17 0 9-7 16-17 16-9 0-17-7-17-16 0-10 8-17 17-17z" id="prefix__path25" fill="#fefefe"/><path class="prefix__fil6" d="M267 448h165c2 0 3 1 3 3 0 1-1 3-3 3H267c-2 0-3-2-3-3 0-2 1-3 3-3z" id="prefix__path27" fill="#006498"/><path class="prefix__fil6" d="M267 415h165c2 0 3 1 3 3 0 1-1 2-3 2H267c-2 0-3-1-3-2 0-2 1-3 3-3z" id="prefix__path29" fill="#006498"/><path class="prefix__fil6" d="M267 381h165c2 0 3 2 3 3 0 2-1 3-3 3H267c-2 0-3-1-3-3 0-1 1-3 3-3z" id="prefix__path31" fill="#006498"/><path class="prefix__fil1" d="M236 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path33" fill="#fefefe"/><path class="prefix__fil1" d="M463 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path35" fill="#fefefe"/><path class="prefix__fil6" id="prefix__polygon37" fill="#006498" d="M305 212h-21v98h21z"/><path d="M477 479v2c0 5-3 8-8 8H231c-5 0-8-3-8-8v-2c0 4 3 8 8 8h238c5 0 8-4 8-8z" id="prefix__path39" fill="#0ea5eb"/><path d="M350 70c155 0 280 125 280 280S505 630 350 630 70 505 70 350 195 70 350 70zm0 46c129 0 234 105 234 234S479 584 350 584 116 479 116 350s105-234 234-234z" id="prefix__path41" fill="#2979ff"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
10
cmd/cmd.go
@@ -1,12 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Execute executes the commands.
|
// Execute executes the commands.
|
||||||
func Execute() {
|
func Execute() error {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
return rootCmd.Execute()
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,18 @@ var cmdsAddCmd = &cobra.Command{
|
|||||||
Short: "Add a command to run on a specific event",
|
Short: "Add a command to run on a specific event",
|
||||||
Long: `Add a command to run on a specific event.`,
|
Long: `Add a command to run on a specific event.`,
|
||||||
Args: cobra.MinimumNArgs(2),
|
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()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
command := strings.Join(args[1:], " ")
|
command := strings.Join(args[1:], " ")
|
||||||
s.Commands[args[0]] = append(s.Commands[args[0]], command)
|
s.Commands[args[0]] = append(s.Commands[args[0]], command)
|
||||||
err = d.store.Settings.Save(s)
|
err = d.store.Settings.Save(s)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,15 @@ var cmdsLsCmd = &cobra.Command{
|
|||||||
Short: "List all commands for each event",
|
Short: "List all commands for each event",
|
||||||
Long: `List all commands for each event.`,
|
Long: `List all commands for each event.`,
|
||||||
Args: cobra.NoArgs,
|
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()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
evt := mustGetString(cmd.Flags(), "event")
|
return err
|
||||||
|
}
|
||||||
|
evt, err := getString(cmd.Flags(), "event")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if evt == "" {
|
if evt == "" {
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
@@ -27,5 +32,6 @@ var cmdsLsCmd = &cobra.Command{
|
|||||||
show["after_"+evt] = s.Commands["after_"+evt]
|
show["after_"+evt] = s.Commands["after_"+evt]
|
||||||
printEvents(show)
|
printEvents(show)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,22 +35,31 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
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()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
evt := args[0]
|
evt := args[0]
|
||||||
|
|
||||||
i, err := strconv.Atoi(args[1])
|
i, err := strconv.Atoi(args[1])
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 3 {
|
if len(args) == 3 {
|
||||||
f, err = strconv.Atoi(args[2])
|
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:]...)
|
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...)
|
||||||
err = d.store.Settings.Save(s)
|
err = d.store.Settings.Save(s)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
200
cmd/config.go
@@ -49,11 +49,18 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
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.disableExternal", false, "disable external links such as GitHub links")
|
||||||
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
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 getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) {
|
||||||
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) {
|
methodStr, err := getString(flags, "auth.method")
|
||||||
method := settings.AuthMethod(mustGetString(flags, "auth.method"))
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
method := settings.AuthMethod(methodStr)
|
||||||
|
|
||||||
var defaultAuther map[string]interface{}
|
var defaultAuther map[string]interface{}
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
@@ -64,83 +71,124 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
|
|||||||
method = def.AuthMethod
|
method = def.AuthMethod
|
||||||
case auth.Auther:
|
case auth.Auther:
|
||||||
ms, err := json.Marshal(def)
|
ms, err := json.Marshal(def)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
err = json.Unmarshal(ms, &defaultAuther)
|
err = json.Unmarshal(ms, &defaultAuther)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var auther auth.Auther
|
return method, defaultAuther, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
|
|
||||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||||
@@ -170,7 +218,10 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||||
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
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, "\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, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
|
||||||
|
fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme)
|
||||||
fmt.Fprintf(w, "\tSorting:\n")
|
fmt.Fprintf(w, "\tSorting:\n")
|
||||||
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
|
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
|
||||||
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)
|
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)
|
||||||
@@ -186,6 +237,9 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
b, err := json.MarshalIndent(auther, "", " ")
|
b, err := json.MarshalIndent(auther, "", " ")
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
|
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",
|
Short: "Prints the configuration",
|
||||||
Long: `Prints the configuration.`,
|
Long: `Prints the configuration.`,
|
||||||
Args: cobra.NoArgs,
|
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()
|
set, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := d.store.Settings.GetServer()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
printSettings(ser, set, auther)
|
return err
|
||||||
|
}
|
||||||
|
return printSettings(ser, set, auther)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,21 @@ var configExportCmd = &cobra.Command{
|
|||||||
json or yaml file. This exported configuration can be changed,
|
json or yaml file. This exported configuration can be changed,
|
||||||
and imported again with 'config import' command.`,
|
and imported again with 'config import' command.`,
|
||||||
Args: jsonYamlArg,
|
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()
|
settings, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
server, err := d.store.Settings.GetServer()
|
server, err := d.store.Settings.GetServer()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
data := &settingsFile{
|
data := &settingsFile{
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
@@ -32,6 +38,9 @@ and imported again with 'config import' command.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = marshal(args[0], data)
|
err = marshal(args[0], data)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,26 +34,35 @@ database.
|
|||||||
|
|
||||||
The path must be for a json or yaml file.`,
|
The path must be for a json or yaml file.`,
|
||||||
Args: jsonYamlArg,
|
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 key []byte
|
||||||
|
var err error
|
||||||
if d.hadDB {
|
if d.hadDB {
|
||||||
settings, err := d.store.Settings.Get()
|
settings, settingErr := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if settingErr != nil {
|
||||||
|
return settingErr
|
||||||
|
}
|
||||||
key = settings.Key
|
key = settings.Key
|
||||||
} else {
|
} else {
|
||||||
key = generateKey()
|
key = generateKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
file := settingsFile{}
|
file := settingsFile{}
|
||||||
err := unmarshal(args[0], &file)
|
err = unmarshal(args[0], &file)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
file.Settings.Key = key
|
file.Settings.Key = key
|
||||||
err = d.store.Settings.Save(file.Settings)
|
err = d.store.Settings.Save(file.Settings)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(file.Server)
|
err = d.store.Settings.SaveServer(file.Server)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var rawAuther interface{}
|
var rawAuther interface{}
|
||||||
if filepath.Ext(args[0]) != ".json" {
|
if filepath.Ext(args[0]) != ".json" {
|
||||||
@@ -63,32 +72,51 @@ The path must be for a json or yaml file.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var auther auth.Auther
|
var auther auth.Auther
|
||||||
|
var autherErr error
|
||||||
switch file.Settings.AuthMethod {
|
switch file.Settings.AuthMethod {
|
||||||
case auth.MethodJSONAuth:
|
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:
|
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:
|
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:
|
case auth.MethodHookAuth:
|
||||||
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth)
|
var a interface{}
|
||||||
|
a, autherErr = getAuther(&auth.HookAuth{}, rawAuther)
|
||||||
|
auther = a.(*auth.HookAuth)
|
||||||
default:
|
default:
|
||||||
checkErr(errors.New("invalid auth method"))
|
return errors.New("invalid auth method")
|
||||||
|
}
|
||||||
|
|
||||||
|
if autherErr != nil {
|
||||||
|
return autherErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Auth.Save(auther)
|
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}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuther(sample auth.Auther, data interface{}) interface{} {
|
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
||||||
authType := reflect.TypeOf(sample)
|
authType := reflect.TypeOf(sample)
|
||||||
auther := reflect.New(authType).Interface()
|
auther := reflect.New(authType).Interface()
|
||||||
bytes, err := json.Marshal(data)
|
bytes, err := json.Marshal(data)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
err = json.Unmarshal(bytes, &auther)
|
err = json.Unmarshal(bytes, &auther)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
return auther
|
return nil, err
|
||||||
|
}
|
||||||
|
return auther, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,52 +22,161 @@ this options can be changed in the future with the command
|
|||||||
to the defaults when creating new users and you don't
|
to the defaults when creating new users and you don't
|
||||||
override the options.`,
|
override the options.`,
|
||||||
Args: cobra.NoArgs,
|
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{}
|
defaults := settings.UserDefaults{}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
getUserDefaults(flags, &defaults, true)
|
err := getUserDefaults(flags, &defaults, true)
|
||||||
authMethod, auther := getAuthentication(flags)
|
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{
|
s := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: key,
|
||||||
Signup: mustGetBool(flags, "signup"),
|
Signup: signup,
|
||||||
CreateUserDir: mustGetBool(flags, "create-user-dir"),
|
CreateUserDir: createUserDir,
|
||||||
MinimumPasswordLength: mustGetUint(flags, "minimum-password-length"),
|
MinimumPasswordLength: minLength,
|
||||||
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
Shell: convertCmdStrToCmdArray(shell),
|
||||||
AuthMethod: authMethod,
|
AuthMethod: authMethod,
|
||||||
Defaults: defaults,
|
Defaults: defaults,
|
||||||
Branding: settings.Branding{
|
Branding: settings.Branding{
|
||||||
Name: mustGetString(flags, "branding.name"),
|
Name: brandingName,
|
||||||
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
DisableExternal: brandingDisableExternal,
|
||||||
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
|
DisableUsedPercentage: brandingDisableUsedPercentage,
|
||||||
Theme: mustGetString(flags, "branding.theme"),
|
Theme: brandingTheme,
|
||||||
Files: mustGetString(flags, "branding.files"),
|
Files: brandingFiles,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ser := &settings.Server{
|
s.FileMode, err = getMode(flags, "file-mode")
|
||||||
Address: mustGetString(flags, "address"),
|
if err != nil {
|
||||||
Socket: mustGetString(flags, "socket"),
|
return err
|
||||||
Root: mustGetString(flags, "root"),
|
|
||||||
BaseURL: mustGetString(flags, "baseurl"),
|
|
||||||
TLSKey: mustGetString(flags, "key"),
|
|
||||||
TLSCert: mustGetString(flags, "cert"),
|
|
||||||
Port: mustGetString(flags, "port"),
|
|
||||||
Log: mustGetString(flags, "log"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := d.store.Settings.Save(s)
|
s.DirMode, err = getMode(flags, "dir-mode")
|
||||||
checkErr(err)
|
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)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = d.store.Auth.Save(auther)
|
err = d.store.Auth.Save(auther)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf(`
|
fmt.Printf(`
|
||||||
Congratulations! You've set up your database to use with File Browser.
|
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
|
Now add your first user via 'filebrowser users add' and then you just
|
||||||
need to call the main command to boot up the server.
|
need to call the main command to boot up the server.
|
||||||
`)
|
`)
|
||||||
printSettings(ser, s, auther)
|
return printSettings(ser, s, auther)
|
||||||
}, pythonConfig{noDB: true}),
|
}, pythonConfig{noDB: true}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,73 +16,105 @@ var configSetCmd = &cobra.Command{
|
|||||||
Long: `Updates the configuration. Set the flags for the options
|
Long: `Updates the configuration. Set the flags for the options
|
||||||
you want to change. Other options will remain unchanged.`,
|
you want to change. Other options will remain unchanged.`,
|
||||||
Args: cobra.NoArgs,
|
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()
|
flags := cmd.Flags()
|
||||||
set, err := d.store.Settings.Get()
|
set, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := d.store.Settings.GetServer()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
hasAuth := false
|
hasAuth := false
|
||||||
flags.Visit(func(flag *pflag.Flag) {
|
flags.Visit(func(flag *pflag.Flag) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
case "baseurl":
|
case "baseurl":
|
||||||
ser.BaseURL = mustGetString(flags, flag.Name)
|
ser.BaseURL, err = getString(flags, flag.Name)
|
||||||
case "root":
|
case "root":
|
||||||
ser.Root = mustGetString(flags, flag.Name)
|
ser.Root, err = getString(flags, flag.Name)
|
||||||
case "socket":
|
case "socket":
|
||||||
ser.Socket = mustGetString(flags, flag.Name)
|
ser.Socket, err = getString(flags, flag.Name)
|
||||||
case "cert":
|
case "cert":
|
||||||
ser.TLSCert = mustGetString(flags, flag.Name)
|
ser.TLSCert, err = getString(flags, flag.Name)
|
||||||
case "key":
|
case "key":
|
||||||
ser.TLSKey = mustGetString(flags, flag.Name)
|
ser.TLSKey, err = getString(flags, flag.Name)
|
||||||
case "address":
|
case "address":
|
||||||
ser.Address = mustGetString(flags, flag.Name)
|
ser.Address, err = getString(flags, flag.Name)
|
||||||
case "port":
|
case "port":
|
||||||
ser.Port = mustGetString(flags, flag.Name)
|
ser.Port, err = getString(flags, flag.Name)
|
||||||
case "log":
|
case "log":
|
||||||
ser.Log = mustGetString(flags, flag.Name)
|
ser.Log, err = getString(flags, flag.Name)
|
||||||
case "signup":
|
case "signup":
|
||||||
set.Signup = mustGetBool(flags, flag.Name)
|
set.Signup, err = getBool(flags, flag.Name)
|
||||||
case "auth.method":
|
case "auth.method":
|
||||||
hasAuth = true
|
hasAuth = true
|
||||||
case "shell":
|
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":
|
case "create-user-dir":
|
||||||
set.CreateUserDir = mustGetBool(flags, flag.Name)
|
set.CreateUserDir, err = getBool(flags, flag.Name)
|
||||||
case "minimum-password-length":
|
case "minimum-password-length":
|
||||||
set.MinimumPasswordLength = mustGetUint(flags, flag.Name)
|
set.MinimumPasswordLength, err = getUint(flags, flag.Name)
|
||||||
case "branding.name":
|
case "branding.name":
|
||||||
set.Branding.Name = mustGetString(flags, flag.Name)
|
set.Branding.Name, err = getString(flags, flag.Name)
|
||||||
case "branding.color":
|
case "branding.color":
|
||||||
set.Branding.Color = mustGetString(flags, flag.Name)
|
set.Branding.Color, err = getString(flags, flag.Name)
|
||||||
case "branding.theme":
|
case "branding.theme":
|
||||||
set.Branding.Theme = mustGetString(flags, flag.Name)
|
set.Branding.Theme, err = getString(flags, flag.Name)
|
||||||
case "branding.disableExternal":
|
case "branding.disableExternal":
|
||||||
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
set.Branding.DisableExternal, err = getBool(flags, flag.Name)
|
||||||
case "branding.disableUsedPercentage":
|
case "branding.disableUsedPercentage":
|
||||||
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
|
set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name)
|
||||||
case "branding.files":
|
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
|
// read the defaults
|
||||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
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
|
// 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)
|
err = d.store.Auth.Save(auther)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = d.store.Settings.Save(set)
|
err = d.store.Settings.Save(set)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
printSettings(ser, set, auther)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return printSettings(ser, set, auther)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
37
cmd/docs.go
@@ -39,12 +39,19 @@ var docsCmd = &cobra.Command{
|
|||||||
Use: "docs",
|
Use: "docs",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
dir := mustGetString(cmd.Flags(), "path")
|
dir, err := getString(cmd.Flags(), "path")
|
||||||
generateDocs(rootCmd, dir)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = generateDocs(rootCmd, dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
names := []string{}
|
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() {
|
if err != nil || info.IsDir() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -56,30 +63,38 @@ var docsCmd = &cobra.Command{
|
|||||||
names = append(names, info.Name())
|
names = append(names, info.Name())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
checkErr(err)
|
|
||||||
printToc(names)
|
printToc(names)
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDocs(cmd *cobra.Command, dir string) {
|
func generateDocs(cmd *cobra.Command, dir string) error {
|
||||||
for _, c := range cmd.Commands() {
|
for _, c := range cmd.Commands() {
|
||||||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
generateDocs(c, dir)
|
err := generateDocs(c, dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
||||||
filename := filepath.Join(dir, basename)
|
filename := filepath.Join(dir, basename)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
defer f.Close()
|
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.InitDefaultHelpCmd()
|
||||||
cmd.InitDefaultHelpFlag()
|
cmd.InitDefaultHelpFlag()
|
||||||
|
|
||||||
@@ -108,7 +123,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
|||||||
|
|
||||||
printOptions(buf, cmd)
|
printOptions(buf, cmd)
|
||||||
_, err := buf.WriteTo(w)
|
_, err := buf.WriteTo(w)
|
||||||
checkErr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ var hashCmd = &cobra.Command{
|
|||||||
Short: "Hashes a password",
|
Short: "Hashes a password",
|
||||||
Long: `Hashes a password using bcrypt algorithm.`,
|
Long: `Hashes a password using bcrypt algorithm.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
pwd, err := users.HashPwd(args[0])
|
pwd, err := users.HashPwd(args[0])
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Println(pwd)
|
fmt.Println(pwd)
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
129
cmd/root.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
"github.com/filebrowser/filebrowser/v2/auth"
|
||||||
"github.com/filebrowser/filebrowser/v2/diskcache"
|
"github.com/filebrowser/filebrowser/v2/diskcache"
|
||||||
|
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/frontend"
|
"github.com/filebrowser/filebrowser/v2/frontend"
|
||||||
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
||||||
"github.com/filebrowser/filebrowser/v2/img"
|
"github.com/filebrowser/filebrowser/v2/img"
|
||||||
@@ -39,6 +41,7 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
|
rootCmd.SilenceUsage = true
|
||||||
cobra.MousetrapHelpText = ""
|
cobra.MousetrapHelpText = ""
|
||||||
|
|
||||||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||||
@@ -112,36 +115,48 @@ set FB_DATABASE.
|
|||||||
Also, if the database path doesn't exist, File Browser will enter into
|
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
|
the quick setup mode and a new database will be bootstrapped and a new
|
||||||
user created with the credentials from options "username" and "password".`,
|
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)
|
log.Println(cfgFile)
|
||||||
|
|
||||||
if !d.hadDB {
|
if !d.hadDB {
|
||||||
quickSetup(cmd.Flags(), d)
|
err := quickSetup(cmd.Flags(), *d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build img service
|
// build img service
|
||||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if workersCount < 1 {
|
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)
|
imgSvc := img.New(workersCount)
|
||||||
|
|
||||||
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if cacheDir != "" {
|
if cacheDir != "" {
|
||||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
||||||
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
||||||
}
|
}
|
||||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
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)
|
setupLog(server.Log)
|
||||||
|
|
||||||
root, err := filepath.Abs(server.Root)
|
root, err := filepath.Abs(server.Root)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
server.Root = root
|
server.Root = root
|
||||||
|
|
||||||
adr := server.Address + ":" + server.Port
|
adr := server.Address + ":" + server.Port
|
||||||
@@ -151,22 +166,34 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
switch {
|
switch {
|
||||||
case server.Socket != "":
|
case server.Socket != "":
|
||||||
listener, err = net.Listen("unix", 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
|
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case server.TLSKey != "" && server.TLSCert != "":
|
case server.TLSKey != "" && server.TLSCert != "":
|
||||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
|
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{
|
listener, err = tls.Listen("tcp", adr, &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
Certificates: []tls.Certificate{cer}},
|
Certificates: []tls.Certificate{cer}},
|
||||||
)
|
)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
listener, err = net.Listen("tcp", adr)
|
listener, err = net.Listen("tcp", adr)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
||||||
@@ -175,7 +202,9 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
@@ -194,8 +223,15 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
sigc := make(chan os.Signal, 1)
|
sigc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigc,
|
||||||
<-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
|
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
|
||||||
defer shutdownRelease()
|
defer shutdownRelease()
|
||||||
@@ -204,13 +240,28 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
log.Fatalf("HTTP shutdown error: %v", err)
|
log.Fatalf("HTTP shutdown error: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Graceful shutdown complete.")
|
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}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//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()
|
server, err := st.Settings.GetServer()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "root"); set {
|
if val, set := getStringParamB(flags, "root"); set {
|
||||||
server.Root = val
|
server.Root = val
|
||||||
@@ -253,7 +304,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isAddrSet && isSocketSet {
|
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.
|
// Do not use saved Socket if address was manually set.
|
||||||
@@ -284,7 +335,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
server.TokenExpirationTime = val
|
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
|
// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||||
@@ -363,7 +414,7 @@ func setupLog(logMethod string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
||||||
log.Println("Performing quick setup")
|
log.Println("Performing quick setup")
|
||||||
|
|
||||||
set := &settings.Settings{
|
set := &settings.Settings{
|
||||||
@@ -373,9 +424,10 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
||||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||||
Defaults: settings.UserDefaults{
|
Defaults: settings.UserDefaults{
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
SingleClick: false,
|
SingleClick: false,
|
||||||
|
AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"),
|
||||||
Perm: users.Permissions{
|
Perm: users.Permissions{
|
||||||
Admin: false,
|
Admin: false,
|
||||||
Execute: true,
|
Execute: true,
|
||||||
@@ -406,10 +458,14 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
set.AuthMethod = auth.MethodJSONAuth
|
set.AuthMethod = auth.MethodJSONAuth
|
||||||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
err = d.store.Auth.Save(&auth.JSONAuth{})
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
checkErr(err)
|
|
||||||
err = d.store.Settings.Save(set)
|
err = d.store.Settings.Save(set)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ser := &settings.Server{
|
ser := &settings.Server{
|
||||||
BaseURL: getStringParam(flags, "baseurl"),
|
BaseURL: getStringParam(flags, "baseurl"),
|
||||||
@@ -422,7 +478,9 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
username := getStringParam(flags, "username")
|
username := getStringParam(flags, "username")
|
||||||
password := getStringParam(flags, "password")
|
password := getStringParam(flags, "password")
|
||||||
@@ -430,11 +488,15 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
if password == "" {
|
if password == "" {
|
||||||
var pwd string
|
var pwd string
|
||||||
pwd, err = users.RandomPwd(set.MinimumPasswordLength)
|
pwd, err = users.RandomPwd(set.MinimumPasswordLength)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("User '%s' initialized with randomly generated password: %s\n", username, pwd)
|
log.Printf("User '%s' initialized with randomly generated password: %s\n", username, pwd)
|
||||||
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
|
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("User '%s' initialize wth user-provided password\n", username)
|
log.Printf("User '%s' initialize wth user-provided password\n", username)
|
||||||
}
|
}
|
||||||
@@ -452,14 +514,15 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
set.Defaults.Apply(user)
|
set.Defaults.Apply(user)
|
||||||
user.Perm.Admin = true
|
user.Perm.Admin = true
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
return d.store.Users.Save(user)
|
||||||
checkErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
if cfgFile == "" {
|
if cfgFile == "" {
|
||||||
home, err := homedir.Dir()
|
home, err := homedir.Dir()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
v.AddConfigPath(".")
|
v.AddConfigPath(".")
|
||||||
v.AddConfigPath(home)
|
v.AddConfigPath(home)
|
||||||
v.AddConfigPath("/etc/filebrowser/")
|
v.AddConfigPath("/etc/filebrowser/")
|
||||||
|
|||||||
@@ -40,27 +40,29 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
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])
|
i, err := strconv.Atoi(args[0])
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
f, err = strconv.Atoi(args[1])
|
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:]...)
|
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||||
err := d.store.Users.Save(u)
|
return d.store.Users.Save(u)
|
||||||
checkErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) {
|
global := func(s *settings.Settings) error {
|
||||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||||
err := d.store.Settings.Save(s)
|
return d.store.Settings.Save(s)
|
||||||
checkErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runRules(d.store, cmd, user, global)
|
return runRules(d.store, cmd, user, global)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
55
cmd/rules.go
@@ -29,41 +29,62 @@ rules.`,
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) {
|
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User) error, globalFn func(*settings.Settings) error) error {
|
||||||
id := getUserIdentifier(cmd.Flags())
|
id, err := getUserIdentifier(cmd.Flags())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if id != nil {
|
if id != nil {
|
||||||
user, err := st.Users.Get("", id)
|
var user *users.User
|
||||||
checkErr(err)
|
user, err = st.Users.Get("", id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if usersFn != nil {
|
if usersFn != nil {
|
||||||
usersFn(user)
|
err = usersFn(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printRules(user.Rules, id)
|
printRules(user.Rules, id)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := st.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if globalFn != nil {
|
if globalFn != nil {
|
||||||
globalFn(s)
|
err = globalFn(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printRules(s.Rules, id)
|
printRules(s.Rules, id)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserIdentifier(flags *pflag.FlagSet) interface{} {
|
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
||||||
id := mustGetUint(flags, "id")
|
id, err := getUint(flags, "id")
|
||||||
username := mustGetString(flags, "username")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
if id != 0 {
|
}
|
||||||
return id
|
username, err := getString(flags, "username")
|
||||||
} else if username != "" {
|
if err != nil {
|
||||||
return username
|
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{}) {
|
func printRules(rulez []rules.Rule, id interface{}) {
|
||||||
|
|||||||
@@ -21,9 +21,15 @@ var rulesAddCmd = &cobra.Command{
|
|||||||
Short: "Add a global rule or user rule",
|
Short: "Add a global rule or user rule",
|
||||||
Long: `Add a global rule or user rule.`,
|
Long: `Add a global rule or user rule.`,
|
||||||
Args: cobra.ExactArgs(1),
|
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 {
|
||||||
allow := mustGetBool(cmd.Flags(), "allow")
|
allow, err := getBool(cmd.Flags(), "allow")
|
||||||
regex := mustGetBool(cmd.Flags(), "regex")
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
regex, err := getBool(cmd.Flags(), "regex")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
exp := args[0]
|
exp := args[0]
|
||||||
|
|
||||||
if regex {
|
if regex {
|
||||||
@@ -41,18 +47,16 @@ var rulesAddCmd = &cobra.Command{
|
|||||||
rule.Path = exp
|
rule.Path = exp
|
||||||
}
|
}
|
||||||
|
|
||||||
user := func(u *users.User) {
|
user := func(u *users.User) error {
|
||||||
u.Rules = append(u.Rules, rule)
|
u.Rules = append(u.Rules, rule)
|
||||||
err := d.store.Users.Save(u)
|
return d.store.Users.Save(u)
|
||||||
checkErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) {
|
global := func(s *settings.Settings) error {
|
||||||
s.Rules = append(s.Rules, rule)
|
s.Rules = append(s.Rules, rule)
|
||||||
err := d.store.Settings.Save(s)
|
return d.store.Settings.Save(s)
|
||||||
checkErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runRules(d.store, cmd, user, global)
|
return runRules(d.store, cmd, user, global)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{
|
|||||||
Short: "List global rules or user specific rules",
|
Short: "List global rules or user specific rules",
|
||||||
Long: `List global rules or user specific rules.`,
|
Long: `List global rules or user specific rules.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
||||||
runRules(d.store, cmd, nil, nil)
|
return runRules(d.store, cmd, nil, nil)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,20 @@ var upgradeCmd = &cobra.Command{
|
|||||||
import share links because they are incompatible with
|
import share links because they are incompatible with
|
||||||
this version.`,
|
this version.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
oldDB := mustGetString(flags, "old.database")
|
oldDB, err := getString(flags, "old.database")
|
||||||
oldConf := mustGetString(flags, "old.config")
|
if err != nil {
|
||||||
err := importer.Import(oldDB, oldConf, getStringParam(flags, "database"))
|
return err
|
||||||
checkErr(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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
62
cmd/users.go
@@ -77,52 +77,67 @@ func addUserFlags(flags *pflag.FlagSet) {
|
|||||||
flags.String("locale", "en", "locale for users")
|
flags.String("locale", "en", "locale for users")
|
||||||
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
||||||
flags.Bool("singleClick", false, "use single clicks only")
|
flags.Bool("singleClick", false, "use single clicks only")
|
||||||
|
flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)")
|
||||||
|
flags.Bool("hideDotfiles", false, "hide dotfiles")
|
||||||
|
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getViewMode(flags *pflag.FlagSet) users.ViewMode {
|
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
||||||
viewMode := users.ViewMode(mustGetString(flags, "viewMode"))
|
viewModeStr, err := getString(flags, "viewMode")
|
||||||
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
|
if err != nil {
|
||||||
checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\""))
|
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
|
//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) {
|
visit := func(flag *pflag.Flag) {
|
||||||
|
if visitErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err error
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
case "scope":
|
case "scope":
|
||||||
defaults.Scope = mustGetString(flags, flag.Name)
|
defaults.Scope, err = getString(flags, flag.Name)
|
||||||
case "locale":
|
case "locale":
|
||||||
defaults.Locale = mustGetString(flags, flag.Name)
|
defaults.Locale, err = getString(flags, flag.Name)
|
||||||
case "viewMode":
|
case "viewMode":
|
||||||
defaults.ViewMode = getViewMode(flags)
|
defaults.ViewMode, err = getViewMode(flags)
|
||||||
case "singleClick":
|
case "singleClick":
|
||||||
defaults.SingleClick = mustGetBool(flags, flag.Name)
|
defaults.SingleClick, err = getBool(flags, flag.Name)
|
||||||
|
case "aceEditorTheme":
|
||||||
|
defaults.AceEditorTheme, err = getString(flags, flag.Name)
|
||||||
case "perm.admin":
|
case "perm.admin":
|
||||||
defaults.Perm.Admin = mustGetBool(flags, flag.Name)
|
defaults.Perm.Admin, err = getBool(flags, flag.Name)
|
||||||
case "perm.execute":
|
case "perm.execute":
|
||||||
defaults.Perm.Execute = mustGetBool(flags, flag.Name)
|
defaults.Perm.Execute, err = getBool(flags, flag.Name)
|
||||||
case "perm.create":
|
case "perm.create":
|
||||||
defaults.Perm.Create = mustGetBool(flags, flag.Name)
|
defaults.Perm.Create, err = getBool(flags, flag.Name)
|
||||||
case "perm.rename":
|
case "perm.rename":
|
||||||
defaults.Perm.Rename = mustGetBool(flags, flag.Name)
|
defaults.Perm.Rename, err = getBool(flags, flag.Name)
|
||||||
case "perm.modify":
|
case "perm.modify":
|
||||||
defaults.Perm.Modify = mustGetBool(flags, flag.Name)
|
defaults.Perm.Modify, err = getBool(flags, flag.Name)
|
||||||
case "perm.delete":
|
case "perm.delete":
|
||||||
defaults.Perm.Delete = mustGetBool(flags, flag.Name)
|
defaults.Perm.Delete, err = getBool(flags, flag.Name)
|
||||||
case "perm.share":
|
case "perm.share":
|
||||||
defaults.Perm.Share = mustGetBool(flags, flag.Name)
|
defaults.Perm.Share, err = getBool(flags, flag.Name)
|
||||||
case "perm.download":
|
case "perm.download":
|
||||||
defaults.Perm.Download = mustGetBool(flags, flag.Name)
|
defaults.Perm.Download, err = getBool(flags, flag.Name)
|
||||||
case "commands":
|
case "commands":
|
||||||
commands, err := flags.GetStringSlice(flag.Name)
|
defaults.Commands, err = flags.GetStringSlice(flag.Name)
|
||||||
checkErr(err)
|
|
||||||
defaults.Commands = commands
|
|
||||||
case "sorting.by":
|
case "sorting.by":
|
||||||
defaults.Sorting.By = mustGetString(flags, flag.Name)
|
defaults.Sorting.By, err = getString(flags, flag.Name)
|
||||||
case "sorting.asc":
|
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 +146,5 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
|||||||
} else {
|
} else {
|
||||||
flags.Visit(visit)
|
flags.Visit(visit)
|
||||||
}
|
}
|
||||||
|
return visitErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,36 +16,69 @@ var usersAddCmd = &cobra.Command{
|
|||||||
Short: "Create a new user",
|
Short: "Create a new user",
|
||||||
Long: `Create a new user and add it to the database.`,
|
Long: `Create a new user and add it to the database.`,
|
||||||
Args: cobra.ExactArgs(2),
|
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()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
return err
|
||||||
|
}
|
||||||
|
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
|
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lockPassword, err := getBool(cmd.Flags(), "lockPassword")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dateFormat, err := getBool(cmd.Flags(), "dateFormat")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDotfiles, err := getBool(cmd.Flags(), "hideDotfiles")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
user := &users.User{
|
user := &users.User{
|
||||||
Username: args[0],
|
Username: args[0],
|
||||||
Password: password,
|
Password: password,
|
||||||
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
LockPassword: lockPassword,
|
||||||
|
DateFormat: dateFormat,
|
||||||
|
HideDotfiles: hideDotfiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Defaults.Apply(user)
|
s.Defaults.Apply(user)
|
||||||
|
|
||||||
servSettings, err := d.store.Settings.GetServer()
|
servSettings, err := d.store.Settings.GetServer()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// since getUserDefaults() polluted s.Defaults.Scope
|
// since getUserDefaults() polluted s.Defaults.Scope
|
||||||
// which makes the Scope not the one saved in the db
|
// which makes the Scope not the one saved in the db
|
||||||
// we need the right s.Defaults.Scope here
|
// we need the right s.Defaults.Scope here
|
||||||
s2, err := d.store.Settings.Get()
|
s2, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
user.Scope = userHome
|
user.Scope = userHome
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,16 @@ var usersExportCmd = &cobra.Command{
|
|||||||
Long: `Export all users to a json or yaml file. Please indicate the
|
Long: `Export all users to a json or yaml file. Please indicate the
|
||||||
path to the file where you want to write the users.`,
|
path to the file where you want to write the users.`,
|
||||||
Args: jsonYamlArg,
|
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("")
|
list, err := d.store.Users.Gets("")
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = marshal(args[0], list)
|
err = marshal(args[0], list)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ var usersFindCmd = &cobra.Command{
|
|||||||
Short: "Find a user by username or id",
|
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.`,
|
Long: `Find a user by username or id. If no flag is set, all users will be printed.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: findUsers,
|
RunE: findUsers,
|
||||||
}
|
}
|
||||||
|
|
||||||
var usersLsCmd = &cobra.Command{
|
var usersLsCmd = &cobra.Command{
|
||||||
Use: "ls",
|
Use: "ls",
|
||||||
Short: "List all users.",
|
Short: "List all users.",
|
||||||
Args: cobra.NoArgs,
|
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 (
|
var (
|
||||||
list []*users.User
|
list []*users.User
|
||||||
user *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("")
|
list, err = d.store.Users.Gets("")
|
||||||
}
|
}
|
||||||
|
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printUsers(list)
|
printUsers(list)
|
||||||
|
return nil
|
||||||
}, pythonConfig{})
|
}, 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
|
installation. For that, just don't place their ID on the files
|
||||||
list or set it to 0.`,
|
list or set it to 0.`,
|
||||||
Args: jsonYamlArg,
|
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])
|
fd, err := os.Open(args[0])
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
list := []*users.User{}
|
list := []*users.User{}
|
||||||
err = unmarshal(args[0], &list)
|
err = unmarshal(args[0], &list)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, user := range list {
|
for _, user := range list {
|
||||||
err = user.Clean("")
|
err = user.Clean("")
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
}
|
return 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
for _, user := range list {
|
||||||
onDB, err := d.store.Users.Get("", user.ID)
|
onDB, err := d.store.Users.Get("", user.ID)
|
||||||
@@ -60,7 +80,7 @@ list or set it to 0.`,
|
|||||||
// User exists in DB.
|
// User exists in DB.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !overwrite {
|
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
|
// If the usernames mismatch, check if there is another one in the DB
|
||||||
@@ -68,7 +88,7 @@ list or set it to 0.`,
|
|||||||
// operation
|
// operation
|
||||||
if user.Username != onDB.Username {
|
if user.Username != onDB.Username {
|
||||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
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 {
|
} else {
|
||||||
@@ -78,8 +98,11 @@ list or set it to 0.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{
|
|||||||
Short: "Delete a user by username or id",
|
Short: "Delete a user by username or id",
|
||||||
Long: `Delete a user by username or id`,
|
Long: `Delete a user by username or id`,
|
||||||
Args: cobra.ExactArgs(1),
|
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])
|
username, id := parseUsernameOrID(args[0])
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -25,7 +25,10 @@ var usersRmCmd = &cobra.Command{
|
|||||||
err = d.store.Users.Delete(id)
|
err = d.store.Users.Delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Println("user deleted successfully")
|
fmt.Println("user deleted successfully")
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,22 @@ var usersUpdateCmd = &cobra.Command{
|
|||||||
Long: `Updates an existing user. Set the flags for the
|
Long: `Updates an existing user. Set the flags for the
|
||||||
options you want to change.`,
|
options you want to change.`,
|
||||||
Args: cobra.ExactArgs(1),
|
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])
|
username, id := parseUsernameOrID(args[0])
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
password := mustGetString(flags, "password")
|
password, err := getString(flags, "password")
|
||||||
newUsername := mustGetString(flags, "username")
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newUsername, err := getString(flags, "username")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
user *users.User
|
user *users.User
|
||||||
@@ -40,7 +48,9 @@ options you want to change.`,
|
|||||||
user, err = d.store.Users.Get("", username)
|
user, err = d.store.Users.Get("", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
defaults := settings.UserDefaults{
|
defaults := settings.UserDefaults{
|
||||||
Scope: user.Scope,
|
Scope: user.Scope,
|
||||||
@@ -51,7 +61,10 @@ options you want to change.`,
|
|||||||
Sorting: user.Sorting,
|
Sorting: user.Sorting,
|
||||||
Commands: user.Commands,
|
Commands: user.Commands,
|
||||||
}
|
}
|
||||||
getUserDefaults(flags, &defaults, false)
|
err = getUserDefaults(flags, &defaults, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
user.Scope = defaults.Scope
|
user.Scope = defaults.Scope
|
||||||
user.Locale = defaults.Locale
|
user.Locale = defaults.Locale
|
||||||
user.ViewMode = defaults.ViewMode
|
user.ViewMode = defaults.ViewMode
|
||||||
@@ -59,7 +72,18 @@ options you want to change.`,
|
|||||||
user.Perm = defaults.Perm
|
user.Perm = defaults.Perm
|
||||||
user.Commands = defaults.Commands
|
user.Commands = defaults.Commands
|
||||||
user.Sorting = defaults.Sorting
|
user.Sorting = defaults.Sorting
|
||||||
user.LockPassword = mustGetBool(flags, "lockPassword")
|
user.LockPassword, err = getBool(flags, "lockPassword")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.DateFormat, err = getBool(flags, "dateFormat")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.HideDotfiles, err = getBool(flags, "hideDotfiles")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if newUsername != "" {
|
if newUsername != "" {
|
||||||
user.Username = newUsername
|
user.Username = newUsername
|
||||||
@@ -67,11 +91,16 @@ options you want to change.`,
|
|||||||
|
|
||||||
if password != "" {
|
if password != "" {
|
||||||
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
|
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Update(user)
|
err = d.store.Users.Update(user)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
|
return nil
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|||||||
72
cmd/utils.go
@@ -4,54 +4,69 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkErr(err error) {
|
const dbPerms = 0640
|
||||||
|
|
||||||
|
func returnErr(err error) error {
|
||||||
if err != nil {
|
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)
|
s, err := flags.GetString(flag)
|
||||||
checkErr(err)
|
return s, returnErr(err)
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
b, err := flags.GetBool(flag)
|
||||||
checkErr(err)
|
return b, returnErr(err)
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
||||||
b, err := flags.GetUint(flag)
|
b, err := flags.GetUint(flag)
|
||||||
checkErr(err)
|
return b, returnErr(err)
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateKey() []byte {
|
func generateKey() []byte {
|
||||||
k, err := settings.GenerateKey()
|
k, err := settings.GenerateKey()
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
type cobraFunc func(cmd *cobra.Command, args []string)
|
type cobraFunc func(cmd *cobra.Command, args []string) error
|
||||||
type pythonFunc func(cmd *cobra.Command, args []string, data pythonData)
|
type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
|
||||||
|
|
||||||
type pythonConfig struct {
|
type pythonConfig struct {
|
||||||
noDB bool
|
noDB bool
|
||||||
@@ -61,6 +76,7 @@ type pythonConfig struct {
|
|||||||
type pythonData struct {
|
type pythonData struct {
|
||||||
hadDB bool
|
hadDB bool
|
||||||
store *storage.Storage
|
store *storage.Storage
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbExists(path string) (bool, error) {
|
func dbExists(path string) (bool, error) {
|
||||||
@@ -84,8 +100,8 @@ func dbExists(path string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||||
return func(cmd *cobra.Command, args []string) {
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
data := pythonData{hadDB: true}
|
data := &pythonData{hadDB: true}
|
||||||
|
|
||||||
path := getStringParam(cmd.Flags(), "database")
|
path := getStringParam(cmd.Flags(), "database")
|
||||||
absPath, err := filepath.Abs(path)
|
absPath, err := filepath.Abs(path)
|
||||||
@@ -106,18 +122,24 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
|||||||
|
|
||||||
log.Println("Using database: " + absPath)
|
log.Println("Using database: " + absPath)
|
||||||
data.hadDB = exists
|
data.hadDB = exists
|
||||||
db, err := storm.Open(path, storm.BoltOptions(files.PermFile, nil))
|
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
data.store, err = bolt.NewStorage(db)
|
data.store, err = bolt.NewStorage(db)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
fn(cmd, args, data)
|
return err
|
||||||
|
}
|
||||||
|
return fn(cmd, args, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshal(filename string, data interface{}) error {
|
func marshal(filename string, data interface{}) error {
|
||||||
fd, err := os.Create(filename)
|
fd, err := os.Create(filename)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
switch ext := filepath.Ext(filename); ext {
|
switch ext := filepath.Ext(filename); ext {
|
||||||
@@ -135,7 +157,9 @@ func marshal(filename string, data interface{}) error {
|
|||||||
|
|
||||||
func unmarshal(filename string, data interface{}) error {
|
func unmarshal(filename string, data interface{}) error {
|
||||||
fd, err := os.Open(filename)
|
fd, err := os.Open(filename)
|
||||||
checkErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
switch ext := filepath.Ext(filename); ext {
|
switch ext := filepath.Ext(filename); ext {
|
||||||
|
|||||||
28
common.mk
@@ -1,28 +0,0 @@
|
|||||||
SHELL := /usr/bin/env bash
|
|
||||||
DATE ?= $(shell date +%FT%T%z)
|
|
||||||
BASE_PATH := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
|
|
||||||
cat $(CURDIR)/.version 2> /dev/null || echo v0)
|
|
||||||
VERSION_HASH = $(shell git rev-parse HEAD)
|
|
||||||
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
|
|
||||||
|
|
||||||
go = GOGC=off go
|
|
||||||
MODULE = $(shell env GO111MODULE=on go list -m)
|
|
||||||
|
|
||||||
# printing
|
|
||||||
# $Q (quiet) is used in the targets as a replacer for @.
|
|
||||||
# This macro helps to print the command for debugging by setting V to 1. Example `make test-unit V=1`
|
|
||||||
V = 0
|
|
||||||
Q = $(if $(filter 1,$V),,@)
|
|
||||||
# $M is a macro to print a colored ▶ character. Example `$(info $(M) running coverage tests…)` will print "▶ running coverage tests…"
|
|
||||||
M = $(shell printf "\033[34;1m▶\033[0m")
|
|
||||||
|
|
||||||
GREEN := $(shell tput -Txterm setaf 2)
|
|
||||||
YELLOW := $(shell tput -Txterm setaf 3)
|
|
||||||
WHITE := $(shell tput -Txterm setaf 7)
|
|
||||||
CYAN := $(shell tput -Txterm setaf 6)
|
|
||||||
RESET := $(shell tput -Txterm sgr0)
|
|
||||||
|
|
||||||
define global_option
|
|
||||||
printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n" $(1) $(2)
|
|
||||||
endef
|
|
||||||
@@ -3,6 +3,15 @@ package errors
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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 (
|
var (
|
||||||
@@ -22,6 +31,10 @@ var (
|
|||||||
ErrInvalidRequestParams = errors.New("invalid request params")
|
ErrInvalidRequestParams = errors.New("invalid request params")
|
||||||
ErrSourceIsParent = errors.New("source is parent")
|
ErrSourceIsParent = errors.New("source is parent")
|
||||||
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
|
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 {
|
type ErrShortPassword struct {
|
||||||
@@ -31,3 +44,44 @@ type ErrShortPassword struct {
|
|||||||
func (e ErrShortPassword) Error() string {
|
func (e ErrShortPassword) Error() string {
|
||||||
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
|
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"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PermFile = 0640
|
|
||||||
const PermDir = 0750
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
||||||
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fileutils
|
package fileutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Copy copies a file or folder from one place to another.
|
// 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 == "" {
|
if src = path.Clean("/" + src); src == "" {
|
||||||
return os.ErrNotExist
|
return os.ErrNotExist
|
||||||
}
|
}
|
||||||
@@ -26,14 +27,14 @@ func Copy(fs afero.Fs, src, dst string) error {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := fs.Stat(src)
|
info, err := afs.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
@@ -9,20 +10,20 @@ import (
|
|||||||
// CopyDir copies a directory from source to dest and all
|
// CopyDir copies a directory from source to dest and all
|
||||||
// of its sub-directories. It doesn't stop if it finds an error
|
// of its sub-directories. It doesn't stop if it finds an error
|
||||||
// during the copy. Returns an error if any.
|
// 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.
|
// Get properties of source.
|
||||||
srcinfo, err := fs.Stat(source)
|
srcinfo, err := afs.Stat(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination directory.
|
// Create the destination directory.
|
||||||
err = fs.MkdirAll(dest, srcinfo.Mode())
|
err = afs.MkdirAll(dest, srcinfo.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, _ := fs.Open(source)
|
dir, _ := afs.Open(source)
|
||||||
obs, err := dir.Readdir(-1)
|
obs, err := dir.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -36,13 +37,13 @@ func CopyDir(fs afero.Fs, source, dest string) error {
|
|||||||
|
|
||||||
if obj.IsDir() {
|
if obj.IsDir() {
|
||||||
// Create sub-directories, recursively.
|
// Create sub-directories, recursively.
|
||||||
err = CopyDir(fs, fsource, fdest)
|
err = CopyDir(afs, fsource, fdest, fileMode, dirMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Perform the file copy.
|
// Perform the file copy.
|
||||||
err = CopyFile(fs, fsource, fdest)
|
err = CopyFile(afs, fsource, fdest, fileMode, dirMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,28 @@ package fileutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MoveFile moves file from src to dst.
|
// MoveFile moves file from src to dst.
|
||||||
// By default the rename filesystem system call is used. If src and dst point to different volumes
|
// 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
|
// the file copy is used as a fallback
|
||||||
func MoveFile(fs afero.Fs, src, dst string) error {
|
func MoveFile(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) error {
|
||||||
if fs.Rename(src, dst) == nil {
|
if afs.Rename(src, dst) == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// fallback
|
// fallback
|
||||||
err := Copy(fs, src, dst)
|
err := Copy(afs, src, dst, fileMode, dirMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = fs.Remove(dst)
|
_ = afs.Remove(dst)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fs.RemoveAll(src); err != nil {
|
if err := afs.RemoveAll(src); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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
|
// CopyFile copies a file from source to dest and returns
|
||||||
// an error if any.
|
// 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.
|
// Open the source file.
|
||||||
src, err := fs.Open(source)
|
src, err := afs.Open(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -42,13 +41,13 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
|||||||
|
|
||||||
// Makes the directory needed to create the dst
|
// Makes the directory needed to create the dst
|
||||||
// file.
|
// file.
|
||||||
err = fs.MkdirAll(filepath.Dir(dest), files.PermDir)
|
err = afs.MkdirAll(filepath.Dir(dest), dirMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination file.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -61,11 +60,11 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy the mode
|
// Copy the mode
|
||||||
info, err := fs.Stat(source)
|
info, err := afs.Stat(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = fs.Chmod(dest, info.Mode())
|
err = afs.Chmod(dest, info.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !dev
|
//go:build !dev
|
||||||
// +build !dev
|
|
||||||
|
|
||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build dev
|
|
||||||
// +build dev
|
|
||||||
|
|
||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var assets fs.FS = os.DirFS("frontend")
|
|
||||||
|
|
||||||
func Assets() fs.FS {
|
|
||||||
return assets
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
|
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/icons/apple-touch-icon.png" />
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/img/icons/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
||||||
|
|
||||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
|
|||||||
@@ -4,37 +4,35 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0",
|
"node": ">=24.0.0",
|
||||||
"pnpm": ">=9.0.0"
|
"pnpm": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "pnpm run typecheck && vite build",
|
"build": "pnpm run typecheck && vite build",
|
||||||
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
||||||
"typecheck": "vue-tsc -p ./tsconfig.tsc.json --noEmit",
|
"typecheck": "vue-tsc -p ./tsconfig.app.json --noEmit",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"lint:fix": "eslint --fix src/",
|
"lint:fix": "eslint --fix src/",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write ."
|
||||||
"test": "playwright test"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chenfengyuan/vue-number-input": "^2.0.1",
|
"@chenfengyuan/vue-number-input": "^2.0.1",
|
||||||
"@vueuse/core": "^12.5.0",
|
"@vueuse/core": "^14.0.0",
|
||||||
"@vueuse/integrations": "^12.5.0",
|
"@vueuse/integrations": "^14.0.0",
|
||||||
"ace-builds": "^1.43.2",
|
"ace-builds": "^1.43.2",
|
||||||
"core-js": "^3.44.0",
|
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"epubjs": "^0.3.93",
|
"epubjs": "^0.3.93",
|
||||||
"filesize": "^10.1.1",
|
"filesize": "^11.0.13",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^15.0.6",
|
"marked": "^17.0.0",
|
||||||
"material-icons": "^1.13.14",
|
"material-icons": "^1.13.14",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^3.0.4",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^7.1.0",
|
||||||
"qrcode.vue": "^3.6.0",
|
"qrcode.vue": "^3.6.0",
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.3.1",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
@@ -50,30 +48,28 @@
|
|||||||
"vue-toastification": "^2.0.0-rc.5"
|
"vue-toastification": "^2.0.0-rc.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
"@intlify/unplugin-vue-i18n": "^11.0.1",
|
||||||
"@playwright/test": "^1.54.1",
|
"@tsconfig/node24": "^24.0.2",
|
||||||
"@tsconfig/node22": "^22.0.2",
|
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.10.10",
|
"@types/node": "^24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||||
"@vitejs/plugin-legacy": "^6.0.0",
|
"@vitejs/plugin-legacy": "^7.2.1",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"concurrently": "^9.2.0",
|
|
||||||
"eslint": "^9.31.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-prettier": "^5.5.1",
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
"eslint-plugin-vue": "^9.24.0",
|
"eslint-plugin-vue": "^10.5.1",
|
||||||
"jsdom": "^26.1.0",
|
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"terser": "^5.43.1",
|
"terser": "^5.43.1",
|
||||||
"vite": "^6.1.6",
|
"typescript": "^5.9.3",
|
||||||
"vite-plugin-compression2": "^1.0.0",
|
"vite": "^7.2.2",
|
||||||
"vue-tsc": "^2.2.0"
|
"vite-plugin-compression2": "^2.3.1",
|
||||||
|
"vue-tsc": "^3.1.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
|
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read environment variables from file.
|
|
||||||
* https://github.com/motdotla/dotenv
|
|
||||||
*/
|
|
||||||
// require('dotenv').config();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: "./tests",
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: "html",
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: "http://127.0.0.1:5173",
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: "on-first-retry",
|
|
||||||
|
|
||||||
/* Set default locale to English (US) */
|
|
||||||
locale: "en-US",
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: "chromium",
|
|
||||||
use: { ...devices["Desktop Chrome"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "firefox",
|
|
||||||
use: { ...devices["Desktop Firefox"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
// {
|
|
||||||
// name: "webkit",
|
|
||||||
// use: { ...devices["Desktop Safari"] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Chrome',
|
|
||||||
// use: { ...devices['Pixel 5'] },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Safari',
|
|
||||||
// use: { ...devices['iPhone 12'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
|
||||||
// {
|
|
||||||
// name: 'Microsoft Edge',
|
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Google Chrome',
|
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
webServer: {
|
|
||||||
command: "npm run dev",
|
|
||||||
url: "http://127.0.0.1:5173",
|
|
||||||
reuseExistingServer: !process.env.CI,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
4237
frontend/pnpm-lock.yaml
generated
@@ -1,147 +1 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="560" height="560" version="1.1" id="prefix__svg44" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision"><defs id="prefix__defs4"><style type="text/css" id="style2">.prefix__fil1{fill:#fefefe}.prefix__fil6{fill:#006498}.prefix__fil5{fill:#bdeaff}</style></defs><g id="prefix__g85" transform="translate(-70 -70)"><path class="prefix__fil1" d="M350 71c154 0 279 125 279 279S504 629 350 629 71 504 71 350 196 71 350 71z" id="prefix__path9" fill="#fefefe"/><path d="M475 236l118 151c3 116-149 252-292 198l-76-99 114-156s138-95 136-94z" id="prefix__path11" fill="#332c2b" fill-opacity=".149"/><path d="M231 211h208l38 24v246c0 5-3 8-8 8H231c-5 0-8-3-8-8V219c0-5 3-8 8-8z" id="prefix__path13" fill="#2bbcff"/><path d="M231 211h208l38 24v2l-37-23H231c-4 0-7 3-7 7v263c-1-1-1-2-1-3V219c0-5 3-8 8-8z" id="prefix__path15" fill="#53c6fc"/><path class="prefix__fil5" id="prefix__polygon17" fill="#bdeaff" d="M305 212h113v98H305z"/><path class="prefix__fil5" d="M255 363h189c3 0 5 2 5 4v116H250V367c0-2 2-4 5-4z" id="prefix__path19" fill="#bdeaff"/><path class="prefix__fil6" id="prefix__polygon21" fill="#006498" d="M250 470h199v13H250z"/><path class="prefix__fil6" d="M380 226h10c3 0 6 2 6 5v40c0 3-3 6-6 6h-10c-3 0-6-3-6-6v-40c0-3 3-5 6-5z" id="prefix__path23" fill="#006498"/><path class="prefix__fil1" d="M254 226c10 0 17 7 17 17 0 9-7 16-17 16-9 0-17-7-17-16 0-10 8-17 17-17z" id="prefix__path25" fill="#fefefe"/><path class="prefix__fil6" d="M267 448h165c2 0 3 1 3 3 0 1-1 3-3 3H267c-2 0-3-2-3-3 0-2 1-3 3-3z" id="prefix__path27" fill="#006498"/><path class="prefix__fil6" d="M267 415h165c2 0 3 1 3 3 0 1-1 2-3 2H267c-2 0-3-1-3-2 0-2 1-3 3-3z" id="prefix__path29" fill="#006498"/><path class="prefix__fil6" d="M267 381h165c2 0 3 2 3 3 0 2-1 3-3 3H267c-2 0-3-1-3-3 0-1 1-3 3-3z" id="prefix__path31" fill="#006498"/><path class="prefix__fil1" d="M236 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path33" fill="#fefefe"/><path class="prefix__fil1" d="M463 472c3 0 5 2 5 5 0 2-2 4-5 4s-5-2-5-4c0-3 2-5 5-5z" id="prefix__path35" fill="#fefefe"/><path class="prefix__fil6" id="prefix__polygon37" fill="#006498" d="M305 212h-21v98h21z"/><path d="M477 479v2c0 5-3 8-8 8H231c-5 0-8-3-8-8v-2c0 4 3 8 8 8h238c5 0 8-4 8-8z" id="prefix__path39" fill="#0ea5eb"/><path d="M350 70c155 0 280 125 280 280S505 630 350 630 70 505 70 350 195 70 350 70zm0 46c129 0 234 105 234 234S479 584 350 584 116 479 116 350s105-234 234-234z" id="prefix__path41" fill="#2979ff"/></g></svg>
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xml:space="preserve"
|
|
||||||
width="560"
|
|
||||||
height="560"
|
|
||||||
version="1.1"
|
|
||||||
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
|
|
||||||
viewBox="0 0 560 560"
|
|
||||||
id="svg44"
|
|
||||||
sodipodi:docname="icon_raw.svg"
|
|
||||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
|
||||||
inkscape:export-filename="/home/umarcor/filebrowser/logo/icon_raw.svg.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96"><metadata
|
|
||||||
id="metadata48"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="711"
|
|
||||||
id="namedview46"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.33714286"
|
|
||||||
inkscape:cx="-172.33051"
|
|
||||||
inkscape:cy="280"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="20"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg44" />
|
|
||||||
<defs
|
|
||||||
id="defs4">
|
|
||||||
<style
|
|
||||||
type="text/css"
|
|
||||||
id="style2">
|
|
||||||
<![CDATA[
|
|
||||||
.fil1 {fill:#FEFEFE}
|
|
||||||
.fil6 {fill:#006498}
|
|
||||||
.fil7 {fill:#0EA5EB}
|
|
||||||
.fil8 {fill:#2979FF}
|
|
||||||
.fil3 {fill:#2BBCFF}
|
|
||||||
.fil0 {fill:#455A64}
|
|
||||||
.fil4 {fill:#53C6FC}
|
|
||||||
.fil5 {fill:#BDEAFF}
|
|
||||||
.fil2 {fill:#332C2B;fill-opacity:0.149020}
|
|
||||||
]]>
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
id="g85"
|
|
||||||
transform="translate(-70,-70)"><path
|
|
||||||
class="fil1"
|
|
||||||
d="M 350,71 C 504,71 629,196 629,350 629,504 504,629 350,629 196,629 71,504 71,350 71,196 196,71 350,71 Z"
|
|
||||||
id="path9"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><path
|
|
||||||
class="fil2"
|
|
||||||
d="M 475,236 593,387 C 596,503 444,639 301,585 L 225,486 339,330 c 0,0 138,-95 136,-94 z"
|
|
||||||
id="path11"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#332c2b;fill-opacity:0.14902003" /><path
|
|
||||||
class="fil3"
|
|
||||||
d="m 231,211 h 208 l 38,24 v 246 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 V 219 c 0,-5 3,-8 8,-8 z"
|
|
||||||
id="path13"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#2bbcff" /><path
|
|
||||||
class="fil4"
|
|
||||||
d="m 231,211 h 208 l 38,24 v 2 L 440,214 H 231 c -4,0 -7,3 -7,7 v 263 c -1,-1 -1,-2 -1,-3 V 219 c 0,-5 3,-8 8,-8 z"
|
|
||||||
id="path15"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#53c6fc" /><polygon
|
|
||||||
class="fil5"
|
|
||||||
points="305,212 418,212 418,310 305,310 "
|
|
||||||
id="polygon17"
|
|
||||||
style="fill:#bdeaff" /><path
|
|
||||||
class="fil5"
|
|
||||||
d="m 255,363 h 189 c 3,0 5,2 5,4 V 483 H 250 V 367 c 0,-2 2,-4 5,-4 z"
|
|
||||||
id="path19"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#bdeaff" /><polygon
|
|
||||||
class="fil6"
|
|
||||||
points="250,470 449,470 449,483 250,483 "
|
|
||||||
id="polygon21"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 380,226 h 10 c 3,0 6,2 6,5 v 40 c 0,3 -3,6 -6,6 h -10 c -3,0 -6,-3 -6,-6 v -40 c 0,-3 3,-5 6,-5 z"
|
|
||||||
id="path23"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil1"
|
|
||||||
d="m 254,226 c 10,0 17,7 17,17 0,9 -7,16 -17,16 -9,0 -17,-7 -17,-16 0,-10 8,-17 17,-17 z"
|
|
||||||
id="path25"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 267,448 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,3 -3,3 H 267 c -2,0 -3,-2 -3,-3 v 0 c 0,-2 1,-3 3,-3 z"
|
|
||||||
id="path27"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 267,415 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,2 -3,2 H 267 c -2,0 -3,-1 -3,-2 v 0 c 0,-2 1,-3 3,-3 z"
|
|
||||||
id="path29"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 267,381 h 165 c 2,0 3,2 3,3 v 0 c 0,2 -1,3 -3,3 H 267 c -2,0 -3,-1 -3,-3 v 0 c 0,-1 1,-3 3,-3 z"
|
|
||||||
id="path31"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil1"
|
|
||||||
d="m 236,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
|
||||||
id="path33"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><path
|
|
||||||
class="fil1"
|
|
||||||
d="m 463,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
|
||||||
id="path35"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><polygon
|
|
||||||
class="fil6"
|
|
||||||
points="305,212 284,212 284,310 305,310 "
|
|
||||||
id="polygon37"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil7"
|
|
||||||
d="m 477,479 v 2 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 v -2 c 0,4 3,8 8,8 h 238 c 5,0 8,-4 8,-8 z"
|
|
||||||
id="path39"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#0ea5eb" /><path
|
|
||||||
class="fil8"
|
|
||||||
d="M 350,70 C 505,70 630,195 630,350 630,505 505,630 350,630 195,630 70,505 70,350 70,195 195,70 350,70 Z m 0,46 C 479,116 584,221 584,350 584,479 479,584 350,584 221,584 116,479 116,350 116,221 221,116 350,116 Z"
|
|
||||||
id="path41"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#2979ff" /></g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -18,9 +18,17 @@
|
|||||||
|
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="[{[ .StaticURL ]}]/img/icons/favicon.svg" />
|
<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="shortcut icon" href="[{[ .StaticURL ]}]/img/icons/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png" />
|
<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" />
|
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
||||||
|
|
||||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default async function search(base: string, query: string) {
|
|||||||
|
|
||||||
let data = await res.json();
|
let data = await res.json();
|
||||||
|
|
||||||
data = data.map((item: UploadItem) => {
|
data = data.map((item: ResourceItem & { dir: boolean }) => {
|
||||||
item.url = `/files${base}` + url.encodePath(item.path);
|
item.url = `/files${base}` + url.encodePath(item.path);
|
||||||
|
|
||||||
if (item.dir) {
|
if (item.dir) {
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import * as tus from "tus-js-client";
|
import * as tus from "tus-js-client";
|
||||||
import { baseURL, tusEndpoint, tusSettings, origin } from "@/utils/constants";
|
import { baseURL, tusEndpoint, tusSettings, origin } from "@/utils/constants";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { useUploadStore } from "@/stores/upload";
|
|
||||||
import { removePrefix } from "@/api/utils";
|
import { removePrefix } from "@/api/utils";
|
||||||
|
|
||||||
const RETRY_BASE_DELAY = 1000;
|
const RETRY_BASE_DELAY = 1000;
|
||||||
const RETRY_MAX_DELAY = 20000;
|
const RETRY_MAX_DELAY = 20000;
|
||||||
const SPEED_UPDATE_INTERVAL = 1000;
|
const CURRENT_UPLOAD_LIST: { [key: string]: tus.Upload } = {};
|
||||||
const ALPHA = 0.2;
|
|
||||||
const ONE_MINUS_ALPHA = 1 - ALPHA;
|
|
||||||
const RECENT_SPEEDS_LIMIT = 5;
|
|
||||||
const MB_DIVISOR = 1024 * 1024;
|
|
||||||
const CURRENT_UPLOAD_LIST: CurrentUploadList = {};
|
|
||||||
|
|
||||||
export async function upload(
|
export async function upload(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
@@ -55,48 +49,35 @@ export async function upload(
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
onError: function (error) {
|
onError: function (error: Error | tus.DetailedError) {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
|
||||||
}
|
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
delete CURRENT_UPLOAD_LIST[filePath];
|
||||||
reject(new Error(`Upload failed: ${error.message}`));
|
|
||||||
|
if (error.message === "Upload aborted") {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof tus.DetailedError
|
||||||
|
? error.originalResponse === null
|
||||||
|
? "000 No connection"
|
||||||
|
: error.originalResponse.getBody()
|
||||||
|
: "Upload failed";
|
||||||
|
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
reject(new Error(message));
|
||||||
},
|
},
|
||||||
onProgress: function (bytesUploaded) {
|
onProgress: function (bytesUploaded) {
|
||||||
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
fileData.currentBytesUploaded = bytesUploaded;
|
|
||||||
|
|
||||||
if (!fileData.hasStarted) {
|
|
||||||
fileData.hasStarted = true;
|
|
||||||
fileData.lastProgressTimestamp = Date.now();
|
|
||||||
|
|
||||||
fileData.interval = window.setInterval(() => {
|
|
||||||
calcProgress(filePath);
|
|
||||||
}, SPEED_UPDATE_INTERVAL);
|
|
||||||
}
|
|
||||||
if (typeof onupload === "function") {
|
if (typeof onupload === "function") {
|
||||||
onupload({ loaded: bytesUploaded });
|
onupload({ loaded: bytesUploaded });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess: function () {
|
onSuccess: function () {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
|
||||||
}
|
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
delete CURRENT_UPLOAD_LIST[filePath];
|
||||||
resolve();
|
resolve();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
CURRENT_UPLOAD_LIST[filePath] = {
|
CURRENT_UPLOAD_LIST[filePath] = upload;
|
||||||
upload: upload,
|
|
||||||
recentSpeeds: [],
|
|
||||||
initialBytesUploaded: 0,
|
|
||||||
currentBytesUploaded: 0,
|
|
||||||
currentAverageSpeed: 0,
|
|
||||||
lastProgressTimestamp: null,
|
|
||||||
sumOfRecentSpeeds: 0,
|
|
||||||
hasStarted: false,
|
|
||||||
interval: undefined,
|
|
||||||
};
|
|
||||||
upload.start();
|
upload.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,76 +109,11 @@ function isTusSupported() {
|
|||||||
return tus.isSupported === true;
|
return tus.isSupported === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeETA(speed?: number) {
|
|
||||||
const state = useUploadStore();
|
|
||||||
if (state.speedMbyte === 0) {
|
|
||||||
return Infinity;
|
|
||||||
}
|
|
||||||
const totalSize = state.sizes.reduce(
|
|
||||||
(acc: number, size: number) => acc + size,
|
|
||||||
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() {
|
|
||||||
let totalSpeed = 0;
|
|
||||||
let totalCount = 0;
|
|
||||||
|
|
||||||
for (const filePath in CURRENT_UPLOAD_LIST) {
|
|
||||||
totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed;
|
|
||||||
totalCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
|
||||||
|
|
||||||
const averageSpeed = totalSpeed / totalCount;
|
|
||||||
const averageETA = computeETA(averageSpeed);
|
|
||||||
|
|
||||||
return { speed: averageSpeed, eta: averageETA };
|
|
||||||
}
|
|
||||||
|
|
||||||
function calcProgress(filePath: string) {
|
|
||||||
const uploadStore = useUploadStore();
|
|
||||||
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
|
|
||||||
const elapsedTime =
|
|
||||||
(Date.now() - (fileData.lastProgressTimestamp ?? 0)) / 1000;
|
|
||||||
const bytesSinceLastUpdate =
|
|
||||||
fileData.currentBytesUploaded - fileData.initialBytesUploaded;
|
|
||||||
const currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime;
|
|
||||||
|
|
||||||
if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) {
|
|
||||||
fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift() ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileData.recentSpeeds.push(currentSpeed);
|
|
||||||
fileData.sumOfRecentSpeeds += currentSpeed;
|
|
||||||
|
|
||||||
const avgRecentSpeed =
|
|
||||||
fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length;
|
|
||||||
fileData.currentAverageSpeed =
|
|
||||||
ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed;
|
|
||||||
|
|
||||||
const { speed, eta } = computeGlobalSpeedAndETA();
|
|
||||||
uploadStore.setUploadSpeed(speed);
|
|
||||||
uploadStore.setETA(eta);
|
|
||||||
|
|
||||||
fileData.initialBytesUploaded = fileData.currentBytesUploaded;
|
|
||||||
fileData.lastProgressTimestamp = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function abortAllUploads() {
|
export function abortAllUploads() {
|
||||||
for (const filePath in CURRENT_UPLOAD_LIST) {
|
for (const filePath in CURRENT_UPLOAD_LIST) {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
if (CURRENT_UPLOAD_LIST[filePath]) {
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
CURRENT_UPLOAD_LIST[filePath].abort(true);
|
||||||
}
|
CURRENT_UPLOAD_LIST[filePath].options!.onError!(
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].upload) {
|
|
||||||
CURRENT_UPLOAD_LIST[filePath].upload.abort(true);
|
|
||||||
CURRENT_UPLOAD_LIST[filePath].upload.options!.onError!(
|
|
||||||
new Error("Upload aborted")
|
new Error("Upload aborted")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,3 +91,21 @@ export function createURL(endpoint: string, searchParams = {}): string {
|
|||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSafeTimeout(callback: () => void, delay: number): number {
|
||||||
|
const MAX_DELAY = 86_400_000;
|
||||||
|
let remaining = delay;
|
||||||
|
|
||||||
|
function scheduleNext(): number {
|
||||||
|
if (remaining <= MAX_DELAY) {
|
||||||
|
return window.setTimeout(callback, remaining);
|
||||||
|
} else {
|
||||||
|
return window.setTimeout(() => {
|
||||||
|
remaining -= MAX_DELAY;
|
||||||
|
scheduleNext();
|
||||||
|
}, MAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduleNext();
|
||||||
|
}
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ import {
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import ProgressBar from "@/components/ProgressBar.vue";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { StatusError } from "@/api/utils.js";
|
|
||||||
|
|
||||||
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||||
|
|
||||||
@@ -181,13 +180,9 @@ export default {
|
|||||||
total: prettyBytes(usage.total, { binary: true }),
|
total: prettyBytes(usage.total, { binary: true }),
|
||||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} finally {
|
||||||
if (error instanceof StatusError && error.is_canceled) {
|
return Object.assign(this.usage, usageStats);
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$showError(error);
|
|
||||||
}
|
}
|
||||||
return Object.assign(this.usage, usageStats);
|
|
||||||
},
|
},
|
||||||
toRoot() {
|
toRoot() {
|
||||||
this.$router.push({ path: "/files" });
|
this.$router.push({ path: "/files" });
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import FileList from "./FileList.vue";
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { removePrefix } from "@/api/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "copy",
|
name: "copy",
|
||||||
@@ -76,7 +77,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(useFileStore, ["req", "selected"]),
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
...mapState(useAuthStore, ["user"]),
|
...mapState(useAuthStore, ["user"]),
|
||||||
...mapWritableState(useFileStore, ["reload"]),
|
...mapWritableState(useFileStore, ["reload", "preselect"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||||
@@ -100,6 +101,7 @@ export default {
|
|||||||
.copy(items, overwrite, rename)
|
.copy(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.success("copy");
|
buttons.success("copy");
|
||||||
|
this.preselect = removePrefix(items[0].to);
|
||||||
|
|
||||||
if (this.$route.path === this.dest) {
|
if (this.$route.path === this.dest) {
|
||||||
this.reload = true;
|
this.reload = true;
|
||||||
|
|||||||
86
frontend/src/components/prompts/CreateFilePath.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="path-container" ref="container">
|
||||||
|
<template v-for="(item, index) in path" :key="index">
|
||||||
|
/
|
||||||
|
<span class="path-item">
|
||||||
|
<span
|
||||||
|
v-if="isDir === true || index < path.length - 1"
|
||||||
|
class="material-icons"
|
||||||
|
>folder
|
||||||
|
</span>
|
||||||
|
<span v-else class="material-icons">insert_drive_file</span>
|
||||||
|
{{ item }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, nextTick, defineProps } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import url from "@/utils/url";
|
||||||
|
|
||||||
|
const fileStore = useFileStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isDir: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const container = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const path = computed(() => {
|
||||||
|
let basePath = fileStore.isFiles ? route.path : url.removeLastDir(route.path);
|
||||||
|
if (!basePath.endsWith("/")) {
|
||||||
|
basePath += "/";
|
||||||
|
}
|
||||||
|
basePath += props.name;
|
||||||
|
return basePath.split("/").filter(Boolean).splice(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(path, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const lastItem = container.value?.lastElementChild;
|
||||||
|
lastItem?.scrollIntoView({ behavior: "auto", inline: "end" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.path-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0.2em 0;
|
||||||
|
gap: 0.25em;
|
||||||
|
overflow-x: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
scrollbar-width: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0.2em 0;
|
||||||
|
gap: 0.25em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-item > span {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -48,16 +48,15 @@ export default {
|
|||||||
"selectedCount",
|
"selectedCount",
|
||||||
"req",
|
"req",
|
||||||
"selected",
|
"selected",
|
||||||
"currentPrompt",
|
|
||||||
]),
|
]),
|
||||||
...mapWritableState(useFileStore, ["reload"]),
|
...mapState(useLayoutStore, ["currentPrompt"]),
|
||||||
|
...mapWritableState(useFileStore, ["reload", "preselect"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
submit: async function () {
|
submit: async function () {
|
||||||
buttons.loading("delete");
|
buttons.loading("delete");
|
||||||
|
|
||||||
window.sessionStorage.setItem("modified", "true");
|
|
||||||
try {
|
try {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
await api.remove(this.$route.path);
|
await api.remove(this.$route.path);
|
||||||
@@ -81,6 +80,12 @@ export default {
|
|||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
|
|
||||||
|
const nearbyItem =
|
||||||
|
this.req.items[Math.max(0, Math.min(this.selected) - 1)];
|
||||||
|
|
||||||
|
this.preselect = nearbyItem?.path;
|
||||||
|
|
||||||
this.reload = true;
|
this.reload = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
|
|||||||
@@ -11,17 +11,26 @@
|
|||||||
@click="closeHovers"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
tabindex="2"
|
tabindex="3"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="button button--flat button--blue"
|
||||||
|
@click="currentPrompt.saveAction"
|
||||||
|
:aria-label="$t('buttons.saveChanges')"
|
||||||
|
:title="$t('buttons.saveChanges')"
|
||||||
|
tabindex="1"
|
||||||
|
>
|
||||||
|
{{ $t("buttons.saveChanges") }}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
id="focus-prompt"
|
id="focus-prompt"
|
||||||
@click="submit"
|
@click="currentPrompt.confirm"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
:aria-label="$t('buttons.discardChanges')"
|
:aria-label="$t('buttons.discardChanges')"
|
||||||
:title="$t('buttons.discardChanges')"
|
:title="$t('buttons.discardChanges')"
|
||||||
tabindex="1"
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.discardChanges") }}
|
{{ $t("buttons.discardChanges") }}
|
||||||
</button>
|
</button>
|
||||||
@@ -30,22 +39,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions } from "pinia";
|
|
||||||
import url from "@/utils/url";
|
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "discardEditorChanges",
|
name: "discardEditorChanges",
|
||||||
|
computed: {
|
||||||
|
...mapState(useLayoutStore, ["currentPrompt"]),
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
...mapActions(useFileStore, ["updateRequest"]),
|
|
||||||
submit: async function () {
|
|
||||||
this.updateRequest(null);
|
|
||||||
|
|
||||||
const uri = url.removeLastDir(this.$route.path) + "/";
|
|
||||||
this.$router.push({ path: uri });
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,9 +25,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "pinia";
|
import { mapState, mapActions } from "pinia";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
@@ -68,6 +69,7 @@ export default {
|
|||||||
this.abortOngoingNext();
|
this.abortOngoingNext();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["showHover"]),
|
||||||
abortOngoingNext() {
|
abortOngoingNext() {
|
||||||
this.nextAbortController.abort();
|
this.nextAbortController.abort();
|
||||||
},
|
},
|
||||||
@@ -163,7 +165,7 @@ export default {
|
|||||||
this.$emit("update:selected", this.selected);
|
this.$emit("update:selected", this.selected);
|
||||||
},
|
},
|
||||||
createDir: async function () {
|
createDir: async function () {
|
||||||
this.$store.commit("showHover", {
|
this.showHover({
|
||||||
prompt: "newDir",
|
prompt: "newDir",
|
||||||
action: null,
|
action: null,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from "pinia";
|
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
@@ -63,6 +63,7 @@ import FileList from "./FileList.vue";
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { removePrefix } from "@/api/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "move",
|
name: "move",
|
||||||
@@ -77,6 +78,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(useFileStore, ["req", "selected"]),
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
...mapState(useAuthStore, ["user"]),
|
...mapState(useAuthStore, ["user"]),
|
||||||
|
...mapWritableState(useFileStore, ["preselect"]),
|
||||||
excludedFolders() {
|
excludedFolders() {
|
||||||
return this.selected
|
return this.selected
|
||||||
.filter((idx) => this.req.items[idx].isDir)
|
.filter((idx) => this.req.items[idx].isDir)
|
||||||
@@ -104,6 +106,7 @@ export default {
|
|||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.success("move");
|
buttons.success("move");
|
||||||
|
this.preselect = removePrefix(items[0].to);
|
||||||
this.$router.push({ path: this.dest });
|
this.$router.push({ path: this.dest });
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
v-model.trim="name"
|
v-model.trim="name"
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
/>
|
/>
|
||||||
|
<CreateFilePath :name="name" :is-dir="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
@@ -48,6 +49,7 @@ import { files as api } from "@/api";
|
|||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import CreateFilePath from "@/components/prompts/CreateFilePath.vue";
|
||||||
|
|
||||||
const $showError = inject<IToastError>("$showError")!;
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
v-model.trim="name"
|
v-model.trim="name"
|
||||||
/>
|
/>
|
||||||
|
<CreateFilePath :name="name" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
@@ -42,6 +43,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import CreateFilePath from "@/components/prompts/CreateFilePath.vue";
|
||||||
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { useFileStore } from "@/stores/file";
|
|||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
import { removePrefix } from "@/api/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "rename",
|
name: "rename",
|
||||||
@@ -65,7 +66,7 @@ export default {
|
|||||||
"selectedCount",
|
"selectedCount",
|
||||||
"isListing",
|
"isListing",
|
||||||
]),
|
]),
|
||||||
...mapWritableState(useFileStore, ["reload"]),
|
...mapWritableState(useFileStore, ["reload", "preselect"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
@@ -97,7 +98,6 @@ export default {
|
|||||||
newLink =
|
newLink =
|
||||||
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||||
|
|
||||||
window.sessionStorage.setItem("modified", "true");
|
|
||||||
try {
|
try {
|
||||||
await api.move([{ from: oldLink, to: newLink }]);
|
await api.move([{ from: oldLink, to: newLink }]);
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
@@ -105,6 +105,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.preselect = removePrefix(newLink);
|
||||||
|
|
||||||
this.reload = true;
|
this.reload = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="filesInUploadCount > 0"
|
v-if="uploadStore.activeUploads.size > 0"
|
||||||
class="upload-files"
|
class="upload-files"
|
||||||
v-bind:class="{ closed: !open }"
|
v-bind:class="{ closed: !open }"
|
||||||
>
|
>
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("prompts.uploadFiles", { files: filesInUploadCount }) }}</h2>
|
<h2>
|
||||||
|
{{
|
||||||
|
$t("prompts.uploadFiles", {
|
||||||
|
files: uploadStore.pendingUploadCount,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</h2>
|
||||||
<div class="upload-info">
|
<div class="upload-info">
|
||||||
<div class="upload-speed">{{ uploadSpeed.toFixed(2) }} MB/s</div>
|
<div class="upload-speed">{{ speedText }}/s</div>
|
||||||
<div class="upload-eta">{{ formattedETA }} remaining</div>
|
<div class="upload-eta">{{ formattedETA }} remaining</div>
|
||||||
<div class="upload-percentage">
|
<div class="upload-percentage">{{ sentPercent }}% Completed</div>
|
||||||
{{ getProgressDecimal }}% Completed
|
|
||||||
</div>
|
|
||||||
<div class="upload-fraction">
|
<div class="upload-fraction">
|
||||||
{{ getTotalProgressBytes }} / {{ getTotalSize }}
|
{{ sentMbytes }} /
|
||||||
|
{{ totalMbytes }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -40,17 +45,21 @@
|
|||||||
<div class="card-content file-icons">
|
<div class="card-content file-icons">
|
||||||
<div
|
<div
|
||||||
class="file"
|
class="file"
|
||||||
v-for="file in filesInUpload"
|
v-for="upload in uploadStore.activeUploads"
|
||||||
:key="file.id"
|
:key="upload.path"
|
||||||
:data-dir="file.isDir"
|
:data-dir="upload.type === 'dir'"
|
||||||
:data-type="file.type"
|
:data-type="upload.type"
|
||||||
:aria-label="file.name"
|
:aria-label="upload.name"
|
||||||
>
|
>
|
||||||
<div class="file-name">
|
<div class="file-name">
|
||||||
<i class="material-icons"></i> {{ file.name }}
|
<i class="material-icons"></i> {{ upload.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="file-progress">
|
<div class="file-progress">
|
||||||
<div v-bind:style="{ width: file.progress + '%' }"></div>
|
<div
|
||||||
|
v-bind:style="{
|
||||||
|
width: (upload.sentBytes / upload.totalBytes) * 100 + '%',
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,63 +67,149 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapState, mapWritableState, mapActions } from "pinia";
|
|
||||||
import { useUploadStore } from "@/stores/upload";
|
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { abortAllUploads } from "@/api/tus";
|
import { useUploadStore } from "@/stores/upload";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { computed, ref, watch } from "vue";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { partial } from "filesize";
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n({});
|
||||||
name: "uploadFiles",
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
open: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useUploadStore, [
|
|
||||||
"filesInUpload",
|
|
||||||
"filesInUploadCount",
|
|
||||||
"uploadSpeed",
|
|
||||||
"getETA",
|
|
||||||
"getProgress",
|
|
||||||
"getProgressDecimal",
|
|
||||||
"getTotalProgressBytes",
|
|
||||||
"getTotalSize",
|
|
||||||
]),
|
|
||||||
...mapWritableState(useFileStore, ["reload"]),
|
|
||||||
formattedETA() {
|
|
||||||
if (!this.getETA || this.getETA === Infinity) {
|
|
||||||
return "--:--:--";
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalSeconds = this.getETA;
|
const open = ref<boolean>(false);
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
const speed = ref<number>(0);
|
||||||
totalSeconds %= 3600;
|
const eta = ref<number>(Infinity);
|
||||||
const minutes = Math.floor(totalSeconds / 60);
|
|
||||||
const seconds = Math.round(totalSeconds % 60);
|
|
||||||
|
|
||||||
return `${hours.toString().padStart(2, "0")}:${minutes
|
const fileStore = useFileStore();
|
||||||
.toString()
|
const uploadStore = useUploadStore();
|
||||||
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
||||||
},
|
const { sentBytes, totalBytes } = storeToRefs(uploadStore);
|
||||||
},
|
|
||||||
methods: {
|
const byteToMbyte = partial({ exponent: 2 });
|
||||||
...mapActions(useUploadStore, ["reset"]), // Mapping reset action from upload store
|
const byteToKbyte = partial({ exponent: 1 });
|
||||||
toggle: function () {
|
|
||||||
this.open = !this.open;
|
const sentPercent = computed(() =>
|
||||||
},
|
((uploadStore.sentBytes / uploadStore.totalBytes) * 100).toFixed(2)
|
||||||
abortAll() {
|
);
|
||||||
if (confirm(this.$t("upload.abortUpload"))) {
|
|
||||||
abortAllUploads();
|
const sentMbytes = computed(() => byteToMbyte(uploadStore.sentBytes));
|
||||||
buttons.done("upload");
|
const totalMbytes = computed(() => byteToMbyte(uploadStore.totalBytes));
|
||||||
this.open = false;
|
const speedText = computed(() => {
|
||||||
this.reset(); // Resetting the upload store state
|
const bytes = speed.value;
|
||||||
this.reload = true; // Trigger reload in the file store
|
|
||||||
}
|
if (bytes < 1024 * 1024) {
|
||||||
},
|
const kb = parseFloat(byteToKbyte(bytes));
|
||||||
},
|
return `${kb.toFixed(2)} KB`;
|
||||||
|
} else {
|
||||||
|
const mb = parseFloat(byteToMbyte(bytes));
|
||||||
|
return `${mb.toFixed(2)} MB`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let lastSpeedUpdate: number = 0;
|
||||||
|
let recentSpeeds: number[] = [];
|
||||||
|
|
||||||
|
let lastThrottleTime = 0;
|
||||||
|
|
||||||
|
const throttledCalculateSpeed = (sentBytes: number, oldSentBytes: number) => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastThrottleTime < 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastThrottleTime = now;
|
||||||
|
calculateSpeed(sentBytes, oldSentBytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateSpeed = (sentBytes: number, oldSentBytes: number) => {
|
||||||
|
// Reset the state when the uploads batch is complete
|
||||||
|
if (sentBytes === 0) {
|
||||||
|
lastSpeedUpdate = 0;
|
||||||
|
recentSpeeds = [];
|
||||||
|
|
||||||
|
eta.value = Infinity;
|
||||||
|
speed.value = 0;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsedTime = (Date.now() - (lastSpeedUpdate ?? 0)) / 1000;
|
||||||
|
const bytesSinceLastUpdate = sentBytes - oldSentBytes;
|
||||||
|
const currentSpeed = bytesSinceLastUpdate / elapsedTime;
|
||||||
|
|
||||||
|
recentSpeeds.push(currentSpeed);
|
||||||
|
if (recentSpeeds.length > 5) {
|
||||||
|
recentSpeeds.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentSpeedsAverage =
|
||||||
|
recentSpeeds.reduce((acc, curr) => acc + curr) / recentSpeeds.length;
|
||||||
|
|
||||||
|
// Use the current speed for the first update to avoid smoothing lag
|
||||||
|
if (recentSpeeds.length === 1) {
|
||||||
|
speed.value = currentSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
speed.value = recentSpeedsAverage * 0.2 + speed.value * 0.8;
|
||||||
|
|
||||||
|
lastSpeedUpdate = Date.now();
|
||||||
|
|
||||||
|
calculateEta();
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateEta = () => {
|
||||||
|
if (speed.value === 0) {
|
||||||
|
eta.value = Infinity;
|
||||||
|
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingSize = uploadStore.totalBytes - uploadStore.sentBytes;
|
||||||
|
const speedBytesPerSecond = speed.value;
|
||||||
|
|
||||||
|
eta.value = remainingSize / speedBytesPerSecond;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(sentBytes, throttledCalculateSpeed);
|
||||||
|
|
||||||
|
watch(totalBytes, (totalBytes, oldTotalBytes) => {
|
||||||
|
if (oldTotalBytes !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the start time of a new upload batch
|
||||||
|
lastSpeedUpdate = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedETA = computed(() => {
|
||||||
|
if (!eta.value || eta.value === Infinity) {
|
||||||
|
return "--:--:--";
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalSeconds = eta.value;
|
||||||
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
|
totalSeconds %= 3600;
|
||||||
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
|
const seconds = Math.round(totalSeconds % 60);
|
||||||
|
|
||||||
|
return `${hours.toString().padStart(2, "0")}:${minutes
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
open.value = !open.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const abortAll = () => {
|
||||||
|
if (confirm(t("upload.abortUpload"))) {
|
||||||
|
buttons.done("upload");
|
||||||
|
open.value = false;
|
||||||
|
uploadStore.abort();
|
||||||
|
fileStore.reload = true; // Trigger reload in the file store
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
28
frontend/src/components/settings/AceEditorTheme.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<select
|
||||||
|
name="selectAceEditorTheme"
|
||||||
|
v-on:change="change"
|
||||||
|
:value="aceEditorTheme"
|
||||||
|
>
|
||||||
|
<option v-for="theme in themes" :value="theme.theme" :key="theme.theme">
|
||||||
|
{{ theme.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type SelectHTMLAttributes } from "vue";
|
||||||
|
import { themes } from "ace-builds/src-noconflict/ext-themelist";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
aceEditorTheme: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "update:aceEditorTheme", val: string | null): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const change = (event: Event) => {
|
||||||
|
emit("update:aceEditorTheme", (event.target as SelectHTMLAttributes)?.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -16,6 +16,7 @@ export default {
|
|||||||
const dataObj = {};
|
const dataObj = {};
|
||||||
const locales = {
|
const locales = {
|
||||||
he: "עברית",
|
he: "עברית",
|
||||||
|
hr: "Hrvatski",
|
||||||
hu: "Magyar",
|
hu: "Magyar",
|
||||||
ar: "العربية",
|
ar: "العربية",
|
||||||
ca: "Català",
|
ca: "Català",
|
||||||
@@ -30,6 +31,7 @@ export default {
|
|||||||
ja: "日本語",
|
ja: "日本語",
|
||||||
ko: "한국어",
|
ko: "한국어",
|
||||||
"nl-be": "Dutch (Belgium)",
|
"nl-be": "Dutch (Belgium)",
|
||||||
|
no: "Norsk",
|
||||||
pl: "Polski",
|
pl: "Polski",
|
||||||
"pt-br": "Português",
|
"pt-br": "Português",
|
||||||
pt: "Português (Brasil)",
|
pt: "Português (Brasil)",
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ main {
|
|||||||
height: 3em;
|
height: 3em;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border-bottom: 1px solid var(--divider);
|
border-bottom: 1px solid var(--divider);
|
||||||
|
position: sticky;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs span,
|
.breadcrumbs span,
|
||||||
|
|||||||
@@ -63,8 +63,8 @@
|
|||||||
local("Roboto"),
|
local("Roboto"),
|
||||||
local("Roboto-Regular"),
|
local("Roboto-Regular"),
|
||||||
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range:
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -75,8 +75,9 @@
|
|||||||
local("Roboto"),
|
local("Roboto"),
|
||||||
local("Roboto-Regular"),
|
local("Roboto-Regular"),
|
||||||
url(../assets/fonts/roboto/normal-latin.woff2) format("woff2");
|
url(../assets/fonts/roboto/normal-latin.woff2) format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
unicode-range:
|
||||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F,
|
||||||
|
U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -142,8 +143,8 @@
|
|||||||
local("Roboto Medium"),
|
local("Roboto Medium"),
|
||||||
local("Roboto-Medium"),
|
local("Roboto-Medium"),
|
||||||
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range:
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -154,8 +155,9 @@
|
|||||||
local("Roboto Medium"),
|
local("Roboto Medium"),
|
||||||
local("Roboto-Medium"),
|
local("Roboto-Medium"),
|
||||||
url(../assets/fonts/roboto/medium-latin.woff2) format("woff2");
|
url(../assets/fonts/roboto/medium-latin.woff2) format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
unicode-range:
|
||||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F,
|
||||||
|
U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -221,8 +223,8 @@
|
|||||||
local("Roboto Bold"),
|
local("Roboto Bold"),
|
||||||
local("Roboto-Bold"),
|
local("Roboto-Bold"),
|
||||||
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range:
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -233,8 +235,9 @@
|
|||||||
local("Roboto Bold"),
|
local("Roboto Bold"),
|
||||||
local("Roboto-Bold"),
|
local("Roboto-Bold"),
|
||||||
url(../assets/fonts/roboto/bold-latin.woff2) format("woff2");
|
url(../assets/fonts/roboto/bold-latin.woff2) format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
unicode-range:
|
||||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F,
|
||||||
|
U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
|
|||||||
@@ -45,6 +45,15 @@
|
|||||||
animation: 0.2s opac forwards;
|
animation: 0.2s opac forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login .logout-message {
|
||||||
|
background: var(--icon-orange);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
animation: 0.2s opac forwards;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes opac {
|
@keyframes opac {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
.md_preview {
|
.md_preview {
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 80vh;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -9,5 +7,5 @@
|
|||||||
|
|
||||||
#preview-container {
|
#preview-container {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 80vh; /* Match the max-height of md_preview for scrolling */
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,6 +329,7 @@ main .spinner .bounce2 {
|
|||||||
#editor-container {
|
#editor-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
@@ -351,6 +352,8 @@ main .spinner .bounce2 {
|
|||||||
#editor-container .breadcrumbs {
|
#editor-container .breadcrumbs {
|
||||||
height: 2.3em;
|
height: 2.3em;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** RTL - flip and position arrow of path ***/
|
/*** RTL - flip and position arrow of path ***/
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "تحديث",
|
"update": "تحديث",
|
||||||
"upload": "رفع",
|
"upload": "رفع",
|
||||||
"openFile": "فتح الملف",
|
"openFile": "فتح الملف",
|
||||||
"discardChanges": "إلغاء التغييرات"
|
"discardChanges": "إلغاء التغييرات",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "تحميل الملف",
|
"downloadFile": "تحميل الملف",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "تسجيل دخول",
|
"submit": "تسجيل دخول",
|
||||||
"username": "إسم المستخدم",
|
"username": "إسم المستخدم",
|
||||||
"usernameTaken": "إسم المستخدم غير متاح",
|
"usernameTaken": "إسم المستخدم غير متاح",
|
||||||
"wrongCredentials": "بيانات دخول خاطئة"
|
"wrongCredentials": "بيانات دخول خاطئة",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "دائم",
|
"permanent": "دائم",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "فيديوهات"
|
"video": "فيديوهات"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "إدارة",
|
"admin": "إدارة",
|
||||||
"administrator": "مدير",
|
"administrator": "مدير",
|
||||||
"allowCommands": "تنفيذ اﻷوامر",
|
"allowCommands": "تنفيذ اﻷوامر",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Actualitzar",
|
"update": "Actualitzar",
|
||||||
"upload": "Pujar",
|
"upload": "Pujar",
|
||||||
"openFile": "Obrir fitxer",
|
"openFile": "Obrir fitxer",
|
||||||
"discardChanges": "Descartar"
|
"discardChanges": "Descartar",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Descarregar fitxer",
|
"downloadFile": "Descarregar fitxer",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Iniciar sessió",
|
"submit": "Iniciar sessió",
|
||||||
"username": "Usuari",
|
"username": "Usuari",
|
||||||
"usernameTaken": "Nom d'usuari no disponible",
|
"usernameTaken": "Nom d'usuari no disponible",
|
||||||
"wrongCredentials": "Usuari i/o contrasenya incorrectes"
|
"wrongCredentials": "Usuari i/o contrasenya incorrectes",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Vídeo"
|
"video": "Vídeo"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrador",
|
"administrator": "Administrador",
|
||||||
"allowCommands": "Executar comandes",
|
"allowCommands": "Executar comandes",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Aktualizovat",
|
"update": "Aktualizovat",
|
||||||
"upload": "Nahrát",
|
"upload": "Nahrát",
|
||||||
"openFile": "Otevřít soubor",
|
"openFile": "Otevřít soubor",
|
||||||
"discardChanges": "Zrušit změny"
|
"discardChanges": "Zrušit změny",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Stáhnout soubor",
|
"downloadFile": "Stáhnout soubor",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Přihlásit se",
|
"submit": "Přihlásit se",
|
||||||
"username": "Uživatelské jméno",
|
"username": "Uživatelské jméno",
|
||||||
"usernameTaken": "Uživatelské jméno již existuje",
|
"usernameTaken": "Uživatelské jméno již existuje",
|
||||||
"wrongCredentials": "Nesprávné přihlašovací údaje"
|
"wrongCredentials": "Nesprávné přihlašovací údaje",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Trvalý",
|
"permanent": "Trvalý",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrátor",
|
"administrator": "Administrátor",
|
||||||
"allowCommands": "Povolit příkazy",
|
"allowCommands": "Povolit příkazy",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Update",
|
"update": "Update",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"openFile": "Datei öffnen",
|
"openFile": "Datei öffnen",
|
||||||
"discardChanges": "Verwerfen"
|
"discardChanges": "Verwerfen",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Download Datei",
|
"downloadFile": "Download Datei",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Login",
|
"submit": "Login",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"usernameTaken": "Benutzername ist bereits vergeben",
|
"usernameTaken": "Benutzername ist bereits vergeben",
|
||||||
"wrongCredentials": "Falsche Zugangsdaten"
|
"wrongCredentials": "Falsche Zugangsdaten",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "Befehle ausführen",
|
"allowCommands": "Befehle ausführen",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Ενημέρωση",
|
"update": "Ενημέρωση",
|
||||||
"upload": "Μεταφόρτωση",
|
"upload": "Μεταφόρτωση",
|
||||||
"openFile": "Άνοιγμα αρχείου",
|
"openFile": "Άνοιγμα αρχείου",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Λήψη αρχείου",
|
"downloadFile": "Λήψη αρχείου",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Είσοδος",
|
"submit": "Είσοδος",
|
||||||
"username": "Όνομα χρήστη",
|
"username": "Όνομα χρήστη",
|
||||||
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
|
"usernameTaken": "Το όνομα χρήστη χρησιμοποιείται ήδη",
|
||||||
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης"
|
"wrongCredentials": "Λάθος όνομα ή/και κωδικός πρόσβασης",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Μόνιμο",
|
"permanent": "Μόνιμο",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Βίντεο"
|
"video": "Βίντεο"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Διαχειριστής",
|
"admin": "Διαχειριστής",
|
||||||
"administrator": "Διαχειριστής",
|
"administrator": "Διαχειριστής",
|
||||||
"allowCommands": "Εκτέλεση εντολών",
|
"allowCommands": "Εκτέλεση εντολών",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Update",
|
"update": "Update",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"openFile": "Open file",
|
"openFile": "Open file",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Download File",
|
"downloadFile": "Download File",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Login",
|
"submit": "Login",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"usernameTaken": "Username already taken",
|
"usernameTaken": "Username already taken",
|
||||||
"wrongCredentials": "Wrong credentials"
|
"wrongCredentials": "Wrong credentials",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "Execute commands",
|
"allowCommands": "Execute commands",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copyFile": "Copiar archivo",
|
"copyFile": "Copiar archivo",
|
||||||
"copyToClipboard": "Copiar al portapapeles",
|
"copyToClipboard": "Copiar al portapapeles",
|
||||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
"copyDownloadLinkToClipboard": "Copiar enlace de descarga al portapapeles",
|
||||||
"create": "Crear",
|
"create": "Crear",
|
||||||
"delete": "Borrar",
|
"delete": "Borrar",
|
||||||
"download": "Descargar",
|
"download": "Descargar",
|
||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
"upload": "Subir",
|
"upload": "Subir",
|
||||||
"openFile": "Abrir archivo",
|
"openFile": "Abrir archivo",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Guardar cambios"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Descargar fichero",
|
"downloadFile": "Descargar fichero",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Iniciar sesión",
|
"submit": "Iniciar sesión",
|
||||||
"username": "Usuario",
|
"username": "Usuario",
|
||||||
"usernameTaken": "Nombre usuario no disponible",
|
"usernameTaken": "Nombre usuario no disponible",
|
||||||
"wrongCredentials": "Usuario y/o contraseña incorrectos"
|
"wrongCredentials": "Usuario y/o contraseña incorrectos",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanente",
|
"permanent": "Permanente",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Vídeo"
|
"video": "Vídeo"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrador",
|
"administrator": "Administrador",
|
||||||
"allowCommands": "Ejecutar comandos",
|
"allowCommands": "Ejecutar comandos",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "به روز سانی",
|
"update": "به روز سانی",
|
||||||
"upload": "آپلود",
|
"upload": "آپلود",
|
||||||
"openFile": "باز کردن فایل",
|
"openFile": "باز کردن فایل",
|
||||||
"discardChanges": "لغو کردن"
|
"discardChanges": "لغو کردن",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "دانلود فایل",
|
"downloadFile": "دانلود فایل",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "ورود",
|
"submit": "ورود",
|
||||||
"username": "نام کاربری",
|
"username": "نام کاربری",
|
||||||
"usernameTaken": "نام کاربری تکراری",
|
"usernameTaken": "نام کاربری تکراری",
|
||||||
"wrongCredentials": "خطا در اعتبارسنجی"
|
"wrongCredentials": "خطا در اعتبارسنجی",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "دائمی",
|
"permanent": "دائمی",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "ویدئو "
|
"video": "ویدئو "
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "اجرای دستورات",
|
"allowCommands": "اجرای دستورات",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Mettre à jour",
|
"update": "Mettre à jour",
|
||||||
"upload": "Importer",
|
"upload": "Importer",
|
||||||
"openFile": "Ouvrir le fichier",
|
"openFile": "Ouvrir le fichier",
|
||||||
"discardChanges": "Annuler"
|
"discardChanges": "Annuler",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Télécharger le fichier",
|
"downloadFile": "Télécharger le fichier",
|
||||||
@@ -77,14 +78,14 @@
|
|||||||
"noPreview": "L'aperçu n'est pas disponible pour ce fichier."
|
"noPreview": "L'aperçu n'est pas disponible pour ce fichier."
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"click": "Sélectionner un élément",
|
"click": "Sélectionner un fichier ou dossier",
|
||||||
"ctrl": {
|
"ctrl": {
|
||||||
"click": "Sélectionner plusieurs éléments",
|
"click": "Sélectionner plusieurs fichiers ou dossiers",
|
||||||
"f": "Ouvrir l'invité de recherche",
|
"f": "Ouvrir l'invité de recherche",
|
||||||
"s": "Télécharger l'élément actuel"
|
"s": "Enregistrer un fichier ou télécharger le dossier actuel"
|
||||||
},
|
},
|
||||||
"del": "Supprimer les éléments sélectionnés",
|
"del": "Supprimer les éléments sélectionnés",
|
||||||
"doubleClick": "Ouvrir un élément",
|
"doubleClick": "Ouvrir un fichier ou dossier",
|
||||||
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
|
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
|
||||||
"f1": "Ouvrir l'aide",
|
"f1": "Ouvrir l'aide",
|
||||||
"f2": "Renommer le fichier",
|
"f2": "Renommer le fichier",
|
||||||
@@ -98,9 +99,12 @@
|
|||||||
"passwordsDontMatch": "Les mots de passe ne concordent pas",
|
"passwordsDontMatch": "Les mots de passe ne concordent pas",
|
||||||
"signup": "S'inscrire",
|
"signup": "S'inscrire",
|
||||||
"submit": "Se connecter",
|
"submit": "Se connecter",
|
||||||
"username": "Utilisateur",
|
"username": "Utilisateur·ice",
|
||||||
"usernameTaken": "Le nom d'utilisateur est déjà pris",
|
"usernameTaken": "Le nom d'utilisateur·ice est déjà pris",
|
||||||
"wrongCredentials": "Identifiants incorrects !"
|
"wrongCredentials": "Identifiants incorrects !",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -110,7 +114,7 @@
|
|||||||
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
|
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
|
||||||
"deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?",
|
"deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?",
|
||||||
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?",
|
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?",
|
||||||
"deleteUser": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
|
"deleteUser": "Êtes-vous sûr de vouloir supprimer cet·te utilisateur·ice ?",
|
||||||
"deleteTitle": "Supprimer",
|
"deleteTitle": "Supprimer",
|
||||||
"displayName": "Nom :",
|
"displayName": "Nom :",
|
||||||
"download": "Télécharger",
|
"download": "Télécharger",
|
||||||
@@ -120,7 +124,7 @@
|
|||||||
"filesSelected": "{count} éléments sélectionnés",
|
"filesSelected": "{count} éléments sélectionnés",
|
||||||
"lastModified": "Dernière modification",
|
"lastModified": "Dernière modification",
|
||||||
"move": "Déplacer",
|
"move": "Déplacer",
|
||||||
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
|
"moveMessage": "Choisissez un nouveau dossier principal pour vos fichier(s)/dossier(s) :",
|
||||||
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
|
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
|
||||||
"newDir": "Nouveau dossier",
|
"newDir": "Nouveau dossier",
|
||||||
"newDirMessage": "Nom du nouveau dossier :",
|
"newDirMessage": "Nom du nouveau dossier :",
|
||||||
@@ -154,13 +158,14 @@
|
|||||||
"video": "Vidéo"
|
"video": "Vidéo"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrateur",
|
"administrator": "Administrateur·ice",
|
||||||
"allowCommands": "Exécuter des commandes",
|
"allowCommands": "Exécuter des commandes",
|
||||||
"allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers",
|
"allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers",
|
||||||
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
||||||
"allowPublish": "Publier de nouveaux posts et pages",
|
"allowPublish": "Publier de nouveaux posts et pages",
|
||||||
"allowSignup": "Autoriser les utilisateurs à s'inscrire",
|
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
|
||||||
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
||||||
"branding": "Image de marque",
|
"branding": "Image de marque",
|
||||||
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
||||||
@@ -169,17 +174,17 @@
|
|||||||
"commandRunner": "Exécuteur de commandes",
|
"commandRunner": "Exécuteur de commandes",
|
||||||
"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}.",
|
"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 !",
|
"commandsUpdated": "Commandes mises à jour !",
|
||||||
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur",
|
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur·ice",
|
||||||
"minimumPasswordLength": "Minimum password length",
|
"minimumPasswordLength": "Taille minimale du mot de passe",
|
||||||
"tusUploads": "Uploads segmentés",
|
"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.",
|
"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.",
|
"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.",
|
||||||
"tusUploadsRetryCount": "Nombre de tentatives en cas d'échec d'un segment.",
|
"tusUploadsRetryCount": "Nombre de tentatives en cas d'échec d'un segment.",
|
||||||
"userHomeBasePath": "Chemin de base pour les répertoires personnels des utilisateurs",
|
"userHomeBasePath": "Chemin de base pour les dossiers personnels des utilisateur·ices",
|
||||||
"userScopeGenerationPlaceholder": "Le périmètre sera généré automatiquement",
|
"userScopeGenerationPlaceholder": "Le périmètre sera généré automatiquement",
|
||||||
"createUserHomeDirectory": "Créer le répertoire personnel de l'utilisateur",
|
"createUserHomeDirectory": "Créer le dossier personnel de l'utilisateur·ice",
|
||||||
"customStylesheet": "Feuille de style personnalisée",
|
"customStylesheet": "Feuille de style personnalisée",
|
||||||
"defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateurs.",
|
"defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateur·ices.",
|
||||||
"disableExternalLinks": "Désactiver les liens externes (sauf la documentation)",
|
"disableExternalLinks": "Désactiver les liens externes (sauf la documentation)",
|
||||||
"disableUsedDiskPercentage": "Désactiver le graphique de pourcentage d'utilisation du disque",
|
"disableUsedDiskPercentage": "Désactiver le graphique de pourcentage d'utilisation du disque",
|
||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
@@ -188,12 +193,12 @@
|
|||||||
"executeOnShellDescription": "Par défaut, File Browser exécute les commandes en appelant directement leurs binaires. Si vous voulez les exécuter sur un shell à la place (comme Bash ou PowerShell), vous pouvez le définir ici avec les arguments et les drapeaux requis. S'il est défini, la commande que vous exécutez sera ajoutée en tant qu'argument. Cela s'applique à la fois aux commandes utilisateur et aux crochets d'événements.",
|
"executeOnShellDescription": "Par défaut, File Browser exécute les commandes en appelant directement leurs binaires. Si vous voulez les exécuter sur un shell à la place (comme Bash ou PowerShell), vous pouvez le définir ici avec les arguments et les drapeaux requis. S'il est défini, la commande que vous exécutez sera ajoutée en tant qu'argument. Cela s'applique à la fois aux commandes utilisateur et aux crochets d'événements.",
|
||||||
"globalRules": "Il s'agit d'un ensemble global de règles d'autorisation et d'interdiction. Elles s'appliquent à tous les utilisateurs. Vous pouvez définir des règles spécifiques sur les paramètres de chaque utilisateur pour remplacer celles-ci.",
|
"globalRules": "Il s'agit d'un ensemble global de règles d'autorisation et d'interdiction. Elles s'appliquent à tous les utilisateurs. Vous pouvez définir des règles spécifiques sur les paramètres de chaque utilisateur pour remplacer celles-ci.",
|
||||||
"globalSettings": "Paramètres globaux",
|
"globalSettings": "Paramètres globaux",
|
||||||
"hideDotfiles": "Cacher les fichiers de configuration utilisateur (dotfiles)",
|
"hideDotfiles": "Cacher les fichiers de configuration commançant par un point",
|
||||||
"insertPath": "Insérer le chemin",
|
"insertPath": "Insérer le chemin",
|
||||||
"insertRegex": "Insérer une expression régulière",
|
"insertRegex": "Insérer une expression régulière",
|
||||||
"instanceName": "Nom de l'instance",
|
"instanceName": "Nom de l'instance",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"lockPassword": "Empêcher l'utilisateur de changer son mot de passe",
|
"lockPassword": "Empêcher l'utilisateur·ice de changer son mot de passe",
|
||||||
"newPassword": "Votre nouveau mot de passe",
|
"newPassword": "Votre nouveau mot de passe",
|
||||||
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
|
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
|
||||||
"newUser": "Nouvel utilisateur",
|
"newUser": "Nouvel utilisateur",
|
||||||
@@ -210,13 +215,13 @@
|
|||||||
"share": "Partager des fichiers"
|
"share": "Partager des fichiers"
|
||||||
},
|
},
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n",
|
"permissionsHelp": "Vous pouvez définir l'utilisateur·ice comme étant administrateur·ice ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur·ice\", toutes les autres options seront automatiquement activées. La gestion des utilisateur·ices est un privilège que seul l'administrateur·ice possède.\n",
|
||||||
"profileSettings": "Paramètres du profil",
|
"profileSettings": "Paramètres du profil",
|
||||||
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers",
|
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers.\n",
|
||||||
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur",
|
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur·ice.",
|
||||||
"rules": "Règles",
|
"rules": "Règles",
|
||||||
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur.\n",
|
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur·ice. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur·ice. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur·ice.\n",
|
||||||
"scope": "Portée du dossier utilisateur",
|
"scope": "Portée du dossier utilisateur·ice",
|
||||||
"setDateFormat": "Définir le format de la date",
|
"setDateFormat": "Définir le format de la date",
|
||||||
"settingsUpdated": "Les paramètres ont été mis à jour !",
|
"settingsUpdated": "Les paramètres ont été mis à jour !",
|
||||||
"shareDuration": "Durée du partage",
|
"shareDuration": "Durée du partage",
|
||||||
@@ -224,21 +229,21 @@
|
|||||||
"shareDeleted": "Partage supprimé !",
|
"shareDeleted": "Partage supprimé !",
|
||||||
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
|
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
|
||||||
"themes": {
|
"themes": {
|
||||||
"default": "System default",
|
"default": "Par défaut du système",
|
||||||
"dark": "Sombre",
|
"dark": "Sombre",
|
||||||
"light": "Clair",
|
"light": "Clair",
|
||||||
"title": "Thème"
|
"title": "Thème"
|
||||||
},
|
},
|
||||||
"user": "Utilisateur",
|
"user": "Utilisateur·ice",
|
||||||
"userCommands": "Commandes",
|
"userCommands": "Commandes",
|
||||||
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :\n",
|
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur·ice. Exemple :\n",
|
||||||
"userCreated": "Utilisateur créé !",
|
"userCreated": "Utilisateur·ice créé !",
|
||||||
"userDefaults": "Paramètres par défaut de l'utilisateur",
|
"userDefaults": "Paramètres par défaut de l'utilisateur.ice",
|
||||||
"userDeleted": "Utilisateur supprimé !",
|
"userDeleted": "Utilisateur·ice supprimé !",
|
||||||
"userManagement": "Gestion des utilisateurs",
|
"userManagement": "Gestion des utilisateur·ices",
|
||||||
"userUpdated": "Utilisateur mis à jour !",
|
"userUpdated": "Utilisateur·ice mis à jour !",
|
||||||
"username": "Nom d'utilisateur",
|
"username": "Nom d'utilisateur·ice",
|
||||||
"users": "Utilisateurs"
|
"users": "Utilisateur·ices"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"help": "Aide",
|
"help": "Aide",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "עדכון",
|
"update": "עדכון",
|
||||||
"upload": "העלאה",
|
"upload": "העלאה",
|
||||||
"openFile": "פתח קובץ",
|
"openFile": "פתח קובץ",
|
||||||
"discardChanges": "זריקת השינויים"
|
"discardChanges": "זריקת השינויים",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "הורד קובץ",
|
"downloadFile": "הורד קובץ",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "התחברות",
|
"submit": "התחברות",
|
||||||
"username": "שם משתמש",
|
"username": "שם משתמש",
|
||||||
"usernameTaken": "שם המשתמש כבר קיים",
|
"usernameTaken": "שם המשתמש כבר קיים",
|
||||||
"wrongCredentials": "פרטי התחברות שגויים"
|
"wrongCredentials": "פרטי התחברות שגויים",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "קבוע",
|
"permanent": "קבוע",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "וידאו"
|
"video": "וידאו"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "מנהל",
|
"admin": "מנהל",
|
||||||
"administrator": "מנהל ראשי",
|
"administrator": "מנהל ראשי",
|
||||||
"allowCommands": "הפעלת פקודות",
|
"allowCommands": "הפעלת פקודות",
|
||||||
|
|||||||
271
frontend/src/i18n/hr.json
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "Otkaži",
|
||||||
|
"clear": "Očisti",
|
||||||
|
"close": "Zatvori",
|
||||||
|
"continue": "Nastavi",
|
||||||
|
"copy": "Kopiraj",
|
||||||
|
"copyFile": "Kopiraj datoteku",
|
||||||
|
"copyToClipboard": "Kopiraj u međuspremnik",
|
||||||
|
"copyDownloadLinkToClipboard": "Kopiraj poveznicu za preuzimanje u međuspremnik",
|
||||||
|
"create": "Stvori",
|
||||||
|
"delete": "Izbriši",
|
||||||
|
"download": "Preuzmi",
|
||||||
|
"file": "Datoteka",
|
||||||
|
"folder": "Mapa",
|
||||||
|
"fullScreen": "Prebaci na cijeli zaslon",
|
||||||
|
"hideDotfiles": "Sakrij datoteke koje započinju točkom",
|
||||||
|
"info": "Info",
|
||||||
|
"more": "Više",
|
||||||
|
"move": "Premjesti",
|
||||||
|
"moveFile": "Premjesti datoteku",
|
||||||
|
"new": "Novo",
|
||||||
|
"next": "Sljedeće",
|
||||||
|
"ok": "OK",
|
||||||
|
"permalink": "Dohvati trajnu poveznicu",
|
||||||
|
"previous": "Prethodno",
|
||||||
|
"preview": "Pregled",
|
||||||
|
"publish": "Objavi",
|
||||||
|
"rename": "Preimenuj",
|
||||||
|
"replace": "Zamijeni",
|
||||||
|
"reportIssue": "Prijavi grešku",
|
||||||
|
"save": "Spremi",
|
||||||
|
"schedule": "Zakaži",
|
||||||
|
"search": "Pretraži",
|
||||||
|
"select": "Označi",
|
||||||
|
"selectMultiple": "Označi više",
|
||||||
|
"share": "Podijeli",
|
||||||
|
"shell": "Promijeni ljusku",
|
||||||
|
"submit": "Predaj",
|
||||||
|
"switchView": "Promijeni prikaz",
|
||||||
|
"toggleSidebar": "Prebaci bočnu traku",
|
||||||
|
"update": "Ažuriraj",
|
||||||
|
"upload": "Prenesi",
|
||||||
|
"openFile": "Otvori datoteku",
|
||||||
|
"discardChanges": "Odbaci",
|
||||||
|
"saveChanges": "Spremi promjene"
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"downloadFile": "Preuzmi Datoteku",
|
||||||
|
"downloadFolder": "Preuzmi Mapu",
|
||||||
|
"downloadSelected": "Preuzmi Odabrano"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"abortUpload": "Jeste li sigurni da hoćete otkazati?"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"forbidden": "Nemate dopuštenje pristupiti ovome.",
|
||||||
|
"internal": "Nešto je stvarno pošlo po zlu.",
|
||||||
|
"notFound": "Lokacija ne može biti dohvaćena.",
|
||||||
|
"connection": "Poslužitelj ne može biti dohvaćen."
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"body": "Tijelo",
|
||||||
|
"closePreview": "Zatvori pregled",
|
||||||
|
"files": "Datoteke",
|
||||||
|
"folders": "Mape",
|
||||||
|
"home": "Dom",
|
||||||
|
"lastModified": "Zadnje izmijenjeno",
|
||||||
|
"loading": "Učitavanje...",
|
||||||
|
"lonely": "Ovdje je tako prazno...",
|
||||||
|
"metadata": "Metapodaci",
|
||||||
|
"multipleSelectionEnabled": "Višestruk odabir",
|
||||||
|
"name": "Naziv",
|
||||||
|
"size": "Veličina",
|
||||||
|
"sortByLastModified": "Sortiraj po zadnjoj izmjeni",
|
||||||
|
"sortByName": "Sortiraj po nazivu",
|
||||||
|
"sortBySize": "Sortiraj po veličini",
|
||||||
|
"noPreview": "Pregled nije dostupan za ovu datoteku."
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"click": "odaberi datoteku ili mapu",
|
||||||
|
"ctrl": {
|
||||||
|
"click": "odaberi više datoteka ili mapa",
|
||||||
|
"f": "tražilica",
|
||||||
|
"s": "spremi datoteku ili preuzmi trenutnu mapu"
|
||||||
|
},
|
||||||
|
"del": "izbriši odabrane stavke",
|
||||||
|
"doubleClick": "otvori datoteku ili mapu",
|
||||||
|
"esc": "očisti odabir i/ili zatvori upit",
|
||||||
|
"f1": "ova informacija",
|
||||||
|
"f2": "preimenuj datoteku",
|
||||||
|
"help": "Pomoć"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"createAnAccount": "Stvori korisnički račun",
|
||||||
|
"loginInstead": "Imam korisnički račun",
|
||||||
|
"password": "Lozinka",
|
||||||
|
"passwordConfirm": "Potvrda lozinke",
|
||||||
|
"passwordsDontMatch": "Lozinke se ne podudaraju",
|
||||||
|
"signup": "Registracija",
|
||||||
|
"submit": "Prijava",
|
||||||
|
"username": "Korisničko ime",
|
||||||
|
"usernameTaken": "Korisničko ime zauzeto",
|
||||||
|
"wrongCredentials": "Neispravno korisničko ime/lozinka",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "Odjavljeni ste zbog neaktivnosti."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permanent": "Trajan",
|
||||||
|
"prompts": {
|
||||||
|
"copy": "Kopiraj",
|
||||||
|
"copyMessage": "Odaberite lokaciju za kopiranje datoteka:",
|
||||||
|
"currentlyNavigating": "Trenutno navigiranje na:",
|
||||||
|
"deleteMessageMultiple": "Jeste li sigurni da želite izbrisati datoteke: {count}?",
|
||||||
|
"deleteMessageSingle": "Jeste li sigurni da hoćete izbrisati ovu datoteku/mapu?",
|
||||||
|
"deleteMessageShare": "Jeste li sigurni da hoćete izbrisati ovo dijeljenje({path})?",
|
||||||
|
"deleteUser": "Jeste li sigurni da hoćete izbrisati ovaj korisnički račun?",
|
||||||
|
"deleteTitle": "Izbriši datoteke",
|
||||||
|
"displayName": "Prikazno Ime:",
|
||||||
|
"download": "Preuzmi datoteke",
|
||||||
|
"downloadMessage": "Odaberite format za preuzimanje.",
|
||||||
|
"error": "Nešto je pošlo po zlu",
|
||||||
|
"fileInfo": "Informacije o datoteci",
|
||||||
|
"filesSelected": "{count} datoteka odabrana.",
|
||||||
|
"lastModified": "Zadnje izmijenjeno",
|
||||||
|
"move": "Premjesti",
|
||||||
|
"moveMessage": "Odaberite novi dom za Vašu datoteku(e)/mapu(e):",
|
||||||
|
"newArchetype": "Stvorite novu objavu na temelju arhetipu. Vaša datoteka bit će stvorena u mapi sadržaja.",
|
||||||
|
"newDir": "Nova mapa",
|
||||||
|
"newDirMessage": "Imenujte Vašu novu mapu.",
|
||||||
|
"newFile": "Nova datoteka",
|
||||||
|
"newFileMessage": "Imenujte Vašu novu datoteku.",
|
||||||
|
"numberDirs": "Broj mapa",
|
||||||
|
"numberFiles": "Broj datoteka",
|
||||||
|
"rename": "Preimenuj",
|
||||||
|
"renameMessage": "Umetni novo ime za",
|
||||||
|
"replace": "Zamijeni",
|
||||||
|
"replaceMessage": "Jedna od datoteka koju pokušavate prenijeti ima sukobljavajući naziv. Želite li preskočiti ovu datoteku i nastaviti s prijenosom ili zamijeniti postojeću datoteku?\n",
|
||||||
|
"schedule": "Zakaži",
|
||||||
|
"scheduleMessage": "Odaberite datum i vrijeme za zakazivanje ove objave.",
|
||||||
|
"show": "Prikaži",
|
||||||
|
"size": "Veličina",
|
||||||
|
"upload": "Prenesi",
|
||||||
|
"uploadFiles": "Prenošenje {files} datoteka...",
|
||||||
|
"uploadMessage": "Odaberite opciju za prijenos.",
|
||||||
|
"optionalPassword": "Opcionalna lozinka",
|
||||||
|
"resolution": "Rezolucija",
|
||||||
|
"discardEditorChanges": "Jeste li sigurni da želite odbaciti promjene koje ste napravili?"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"images": "Slike",
|
||||||
|
"music": "Glazba",
|
||||||
|
"pdf": "PDF",
|
||||||
|
"pressToSearch": "Pritisnite enter za pretraživanje...",
|
||||||
|
"search": "Pretraživanje...",
|
||||||
|
"typeToSearch": "Tipkajte za pretraživanje...",
|
||||||
|
"types": "Tipovi",
|
||||||
|
"video": "Video"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
|
"admin": "Admin",
|
||||||
|
"administrator": "Administrator",
|
||||||
|
"allowCommands": "Izvrši naredbe",
|
||||||
|
"allowEdit": "Uredi, preimenuj i izbriši datoteke ili mape",
|
||||||
|
"allowNew": "Stvori nove datoteke i mape",
|
||||||
|
"allowPublish": "Objavi nove objave i stranice",
|
||||||
|
"allowSignup": "Dopusti registraciju korisnicima",
|
||||||
|
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
|
||||||
|
"branding": "Brendiranje",
|
||||||
|
"brandingDirectoryPath": "Put brendiranja",
|
||||||
|
"brandingHelp": "Možete prilagoditi izgled i funkcionalnost Vašeg File Browsera mijenjanjem njegovog naziva, zamjenom logotipa, dodavanjem prilagođenih stilova pa čak i onemogućavanjem vanjskih poveznica na GitHub.\nZa više informacija o prilagođenome brendiranju pogledajte {0}.",
|
||||||
|
"changePassword": "Promjena lozinke",
|
||||||
|
"commandRunner": "Izvršitelj naredbi",
|
||||||
|
"commandRunnerHelp": "Ovdje možete postaviti naredbe koje se izvršuju u imenovanim događajima. Morate napisati jednu po liniji. Varijable okruženja {0} i {1} bit će dostupne, tako da je {0} relativna {1}. Za više informacija o ovoj značajci pogledajte {2}.",
|
||||||
|
"commandsUpdated": "Naredbe ažurirane!",
|
||||||
|
"createUserDir": "Automatsko stvaranje kućne mape korisnika pri dodavanju novog korisnika",
|
||||||
|
"minimumPasswordLength": "Minimalna duljina lozinke",
|
||||||
|
"tusUploads": "Segmentirani prijenosi",
|
||||||
|
"tusUploadsHelp": "File Browser podržava segmentirane prijenose datoteka, omogućavajući stvaranje učinkovitih, pouzdanih, obnovljivih i segmentiranih prijenosa datoteka čak i na nepouzdanim mrežama.",
|
||||||
|
"tusUploadsChunkSize": "Naznačuje maksimalnu veličinu zahtjeva (direktni prijenosi bit će korišteni za manje prijenose). Možete unijeti cijeli broj koji označava veličinu bajta ili niz znakova poput 10MB, 1GB itd.",
|
||||||
|
"tusUploadsRetryCount": "Broj ponovnih pokušaja ako se dio ne uspije prenijeti.",
|
||||||
|
"userHomeBasePath": "Bazni put za kućne mape korisnika",
|
||||||
|
"userScopeGenerationPlaceholder": "Opseg će se automatski generirati",
|
||||||
|
"createUserHomeDirectory": "Stvori kućnu mapu korisnika",
|
||||||
|
"customStylesheet": "Prilagođeni Stylesheet",
|
||||||
|
"defaultUserDescription": "Zadane postavke za nove korisnike.",
|
||||||
|
"disableExternalLinks": "Onemogući vanjske poveznice (osim dokumentacije)",
|
||||||
|
"disableUsedDiskPercentage": "Onemogući graf iskorištenosti diska",
|
||||||
|
"documentation": "dokumentacija",
|
||||||
|
"examples": "Primjeri",
|
||||||
|
"executeOnShell": "Izvrši u ljusci",
|
||||||
|
"executeOnShellDescription": "Po zadanim postavkama, File Browser izvršava naredbe izravnim pozivanjem njihovih binarnih datoteka. Ako ih želite izvršiti u ljusci (kao što su Bash ili PowerShell), možete ih definirati ovdje s potrebnim argumentima i oznakama. Ako je postavljena, naredba koju izvršavate bit će dodana kao argument. To se odnosi i na korisničke naredbe i na događajne kuke.",
|
||||||
|
"globalRules": "Ovo je globalan skup pravila dopuštanja i zabrane. Primjenjuju se na svakog korisnika. Moguće je definirati specifična pravila u postavkama svakog korisnika da biste nadjačali ove postavke.",
|
||||||
|
"globalSettings": "Globalne postavke",
|
||||||
|
"hideDotfiles": "Sakrij datoteke koje započinju točkom",
|
||||||
|
"insertPath": "Umetni put",
|
||||||
|
"insertRegex": "Umetni regex izraz",
|
||||||
|
"instanceName": "Naziv instance",
|
||||||
|
"language": "Jezik",
|
||||||
|
"lockPassword": "Onemogući mijenjanje lozinke korisniku",
|
||||||
|
"newPassword": "Vaša nova lozinka",
|
||||||
|
"newPasswordConfirm": "Potvrdite Vašu novu lozinku",
|
||||||
|
"newUser": "Novi Korisnik",
|
||||||
|
"password": "Lozinka",
|
||||||
|
"passwordUpdated": "Lozinka ažurirana!",
|
||||||
|
"path": "Put",
|
||||||
|
"perm": {
|
||||||
|
"create": "Stvaranje datoteka i mapa",
|
||||||
|
"delete": "Brisanje datoteka i mapa",
|
||||||
|
"download": "Preuzimanje",
|
||||||
|
"execute": "Izvršavanje naredbi",
|
||||||
|
"modify": "Uređivanje datoteka",
|
||||||
|
"rename": "Preimenovanje ili premještanje datoteka i mapa",
|
||||||
|
"share": "Dijeljenje datoteka"
|
||||||
|
},
|
||||||
|
"permissions": "Dopuštenja",
|
||||||
|
"permissionsHelp": "Korisnika možete postaviti administratorom ili odabrati dopuštenja individualno. Odabirom na \"Administrator\", sve druge opcije bit će automatski odabrane. Upravljanje korisnicima ostaje privilegija administratora.\n",
|
||||||
|
"profileSettings": "Postavke profila",
|
||||||
|
"ruleExample1": "onemogućava pristup svakoj datoteci koja započinje točkom (poput .git, .gitignore) u svakoj mapi.\n",
|
||||||
|
"ruleExample2": "blokira pristup datoteci naziva Caddyfile na korijenu opsega.",
|
||||||
|
"rules": "Pravila",
|
||||||
|
"rulesHelp": "Ovdje možete definirati skup pravila dopuštanja i zabrane za ovog specifičnog korisnika. Blokirane datoteke neće se prikazivati u popisima i neće biti dostupne korisniku. Podržavamo regex i puteve relativne opsegu korisnika.\n",
|
||||||
|
"scope": "Opseg",
|
||||||
|
"setDateFormat": "Odredi točan format datuma",
|
||||||
|
"settingsUpdated": "Postavke ažurirane!",
|
||||||
|
"shareDuration": "Podijeli Trajanje",
|
||||||
|
"shareManagement": "Upravljanje Dijeljenjem",
|
||||||
|
"shareDeleted": "Podjela izbrisana!",
|
||||||
|
"singleClick": "Koristi jednostruke klikove za otvaranje datoteka i mapa",
|
||||||
|
"themes": {
|
||||||
|
"default": "Zadano - Sustav",
|
||||||
|
"dark": "Tamno",
|
||||||
|
"light": "Svijetlo",
|
||||||
|
"title": "Tema"
|
||||||
|
},
|
||||||
|
"user": "Korisnik",
|
||||||
|
"userCommands": "Naredbe",
|
||||||
|
"userCommandsHelp": "Popis dostupnih naredbi za ovog korisnika. Primjer:\n",
|
||||||
|
"userCreated": "Korisnik stvoren!",
|
||||||
|
"userDefaults": "Zadane postavke korisnika",
|
||||||
|
"userDeleted": "Korisnik izbrisan!",
|
||||||
|
"userManagement": "Upravljanje Korisnicima",
|
||||||
|
"userUpdated": "Korisnik ažuriran!",
|
||||||
|
"username": "Korisničko ime",
|
||||||
|
"users": "Korisnici"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"help": "Pomoć",
|
||||||
|
"hugoNew": "Hugo New",
|
||||||
|
"login": "Prijava",
|
||||||
|
"logout": "Odjava",
|
||||||
|
"myFiles": "Moje datoteke",
|
||||||
|
"newFile": "Nova datoteka",
|
||||||
|
"newFolder": "Nova mapa",
|
||||||
|
"preview": "Pregled",
|
||||||
|
"settings": "Postavke",
|
||||||
|
"signup": "Registracija",
|
||||||
|
"siteSettings": "Postavke stranice"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"linkCopied": "Poveznica kopirana!"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"days": "Dani",
|
||||||
|
"hours": "Sati",
|
||||||
|
"minutes": "Minute",
|
||||||
|
"seconds": "Sekunde",
|
||||||
|
"unit": "Jedinica vremena"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Frissítés",
|
"update": "Frissítés",
|
||||||
"upload": "Feltöltés",
|
"upload": "Feltöltés",
|
||||||
"openFile": "Fájl megnyitása",
|
"openFile": "Fájl megnyitása",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Fájl letöltése",
|
"downloadFile": "Fájl letöltése",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Belépés",
|
"submit": "Belépés",
|
||||||
"username": "Felhasználói név",
|
"username": "Felhasználói név",
|
||||||
"usernameTaken": "A felhasználói név már foglalt",
|
"usernameTaken": "A felhasználói név már foglalt",
|
||||||
"wrongCredentials": "Hibás hitelesítő adatok"
|
"wrongCredentials": "Hibás hitelesítő adatok",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Állandó",
|
"permanent": "Állandó",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Videó"
|
"video": "Videó"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Adminisztrátor",
|
"administrator": "Adminisztrátor",
|
||||||
"allowCommands": "Parancsok futtatása",
|
"allowCommands": "Parancsok futtatása",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import("dayjs/locale/en");
|
|||||||
import("dayjs/locale/es");
|
import("dayjs/locale/es");
|
||||||
import("dayjs/locale/fr");
|
import("dayjs/locale/fr");
|
||||||
import("dayjs/locale/he");
|
import("dayjs/locale/he");
|
||||||
|
import("dayjs/locale/hr");
|
||||||
import("dayjs/locale/hu");
|
import("dayjs/locale/hu");
|
||||||
import("dayjs/locale/is");
|
import("dayjs/locale/is");
|
||||||
import("dayjs/locale/it");
|
import("dayjs/locale/it");
|
||||||
@@ -27,6 +28,7 @@ import("dayjs/locale/vi");
|
|||||||
import("dayjs/locale/zh-cn");
|
import("dayjs/locale/zh-cn");
|
||||||
import("dayjs/locale/zh-tw");
|
import("dayjs/locale/zh-tw");
|
||||||
import("dayjs/locale/cs");
|
import("dayjs/locale/cs");
|
||||||
|
import("dayjs/locale/nb");
|
||||||
|
|
||||||
// All i18n resources specified in the plugin `include` option can be loaded
|
// All i18n resources specified in the plugin `include` option can be loaded
|
||||||
// at once using the import syntax
|
// at once using the import syntax
|
||||||
@@ -40,6 +42,9 @@ export function detectLocale() {
|
|||||||
case /^he\b/.test(locale):
|
case /^he\b/.test(locale):
|
||||||
locale = "he";
|
locale = "he";
|
||||||
break;
|
break;
|
||||||
|
case /^hr\b/.test(locale):
|
||||||
|
locale = "hr";
|
||||||
|
break;
|
||||||
case /^hu\b/.test(locale):
|
case /^hu\b/.test(locale):
|
||||||
locale = "hu";
|
locale = "hu";
|
||||||
break;
|
break;
|
||||||
@@ -101,7 +106,6 @@ export function detectLocale() {
|
|||||||
case /^tr\b/.test(locale):
|
case /^tr\b/.test(locale):
|
||||||
locale = "tr";
|
locale = "tr";
|
||||||
break;
|
break;
|
||||||
// ua wasnt a valid locale for ukraine
|
|
||||||
case /^uk\b/.test(locale):
|
case /^uk\b/.test(locale):
|
||||||
locale = "uk";
|
locale = "uk";
|
||||||
break;
|
break;
|
||||||
@@ -115,6 +119,10 @@ export function detectLocale() {
|
|||||||
case /^nl-be\b/.test(locale):
|
case /^nl-be\b/.test(locale):
|
||||||
locale = "nl-be";
|
locale = "nl-be";
|
||||||
break;
|
break;
|
||||||
|
case /^nb\b/.test(locale):
|
||||||
|
case /^no\b/.test(locale):
|
||||||
|
locale = "no";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
locale = "en";
|
locale = "en";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Vista",
|
"update": "Vista",
|
||||||
"upload": "Hlaða upp",
|
"upload": "Hlaða upp",
|
||||||
"openFile": "Open file",
|
"openFile": "Open file",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Sækja skjal",
|
"downloadFile": "Sækja skjal",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Innskráning",
|
"submit": "Innskráning",
|
||||||
"username": "Notendanafn",
|
"username": "Notendanafn",
|
||||||
"usernameTaken": "Þetta norendanafn er þegar í notkun",
|
"usernameTaken": "Þetta norendanafn er þegar í notkun",
|
||||||
"wrongCredentials": "Rangar notendaupplýsingar"
|
"wrongCredentials": "Rangar notendaupplýsingar",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Varanlegt",
|
"permanent": "Varanlegt",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Myndbönd"
|
"video": "Myndbönd"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Stjórnandi",
|
"admin": "Stjórnandi",
|
||||||
"administrator": "Stjórnandi",
|
"administrator": "Stjórnandi",
|
||||||
"allowCommands": "Senda skipanir",
|
"allowCommands": "Senda skipanir",
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
"copy": "Copia",
|
"copy": "Copia",
|
||||||
"copyFile": "Copia file",
|
"copyFile": "Copia file",
|
||||||
"copyToClipboard": "Copia negli appunti",
|
"copyToClipboard": "Copia negli appunti",
|
||||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
"copyDownloadLinkToClipboard": "Copia link di scarica negli appunti",
|
||||||
"create": "Crea",
|
"create": "Crea",
|
||||||
"delete": "Elimina",
|
"delete": "Elimina",
|
||||||
"download": "Scarica",
|
"download": "Scarica",
|
||||||
"file": "File",
|
"file": "File",
|
||||||
"folder": "Folder",
|
"folder": "Cartella",
|
||||||
"fullScreen": "Toggle full screen",
|
"fullScreen": "Abilita schermo intero",
|
||||||
"hideDotfiles": "Nascondi dotfile",
|
"hideDotfiles": "Nascondi dotfile",
|
||||||
"info": "Informazioni",
|
"info": "Informazioni",
|
||||||
"more": "Altro",
|
"more": "Altro",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"permalink": "Ottieni link permanente",
|
"permalink": "Ottieni link permanente",
|
||||||
"previous": "Precedente",
|
"previous": "Precedente",
|
||||||
"preview": "Preview",
|
"preview": "Anteprima",
|
||||||
"publish": "Publica",
|
"publish": "Publica",
|
||||||
"rename": "Rinomina",
|
"rename": "Rinomina",
|
||||||
"replace": "Sostituisci",
|
"replace": "Sostituisci",
|
||||||
@@ -36,13 +36,14 @@
|
|||||||
"selectMultiple": "Seleziona molteplici",
|
"selectMultiple": "Seleziona molteplici",
|
||||||
"share": "Condividi",
|
"share": "Condividi",
|
||||||
"shell": "Mostra/nascondi shell",
|
"shell": "Mostra/nascondi shell",
|
||||||
"submit": "Submit",
|
"submit": "Invia",
|
||||||
"switchView": "Cambia vista",
|
"switchView": "Cambia vista",
|
||||||
"toggleSidebar": "Mostra/nascondi la barra laterale",
|
"toggleSidebar": "Mostra/nascondi la barra laterale",
|
||||||
"update": "Aggiorna",
|
"update": "Aggiorna",
|
||||||
"upload": "Carica",
|
"upload": "Carica",
|
||||||
"openFile": "Open file",
|
"openFile": "Apri file",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Ignora",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Scarica file",
|
"downloadFile": "Scarica file",
|
||||||
@@ -50,13 +51,13 @@
|
|||||||
"downloadSelected": "Scarica selezionati"
|
"downloadSelected": "Scarica selezionati"
|
||||||
},
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
"abortUpload": "Are you sure you wish to abort?"
|
"abortUpload": "Sei sicuro di voler abortire la procedura?"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"forbidden": "Non hai i permessi per accedere a questo file.",
|
"forbidden": "Non hai i permessi per accedere a questo file.",
|
||||||
"internal": "Qualcosa è andato veramente male.",
|
"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."
|
"connection": "Il server non è raggiungibile"
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Contenuto",
|
"body": "Contenuto",
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
"sortByLastModified": "Ordina per ultima modifica",
|
"sortByLastModified": "Ordina per ultima modifica",
|
||||||
"sortByName": "Ordina per nome",
|
"sortByName": "Ordina per nome",
|
||||||
"sortBySize": "Ordina per dimensione",
|
"sortBySize": "Ordina per dimensione",
|
||||||
"noPreview": "Preview is not available for this file."
|
"noPreview": "L'anteprima non è disponibile per questo file."
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"click": "seleziona un file o una cartella",
|
"click": "seleziona un file o una cartella",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Entra",
|
"submit": "Entra",
|
||||||
"username": "Nome utente",
|
"username": "Nome utente",
|
||||||
"usernameTaken": "Username già usato",
|
"usernameTaken": "Username già usato",
|
||||||
"wrongCredentials": "Credenziali errate"
|
"wrongCredentials": "Credenziali errate",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanente",
|
"permanent": "Permanente",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -109,8 +113,8 @@
|
|||||||
"currentlyNavigating": "Attualmente navigando su:",
|
"currentlyNavigating": "Attualmente navigando su:",
|
||||||
"deleteMessageMultiple": "Sei sicuro di voler eliminare {count} file?",
|
"deleteMessageMultiple": "Sei sicuro di voler eliminare {count} file?",
|
||||||
"deleteMessageSingle": "Sei sicuro di voler eliminare questo file/cartella?",
|
"deleteMessageSingle": "Sei sicuro di voler eliminare questo file/cartella?",
|
||||||
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
"deleteMessageShare": "Sei sicuro di voler eliminare questo percorso condiviso ({path})?",
|
||||||
"deleteUser": "Are you sure you want to delete this user?",
|
"deleteUser": "Sei sicuro di voler eliminare questo utente?",
|
||||||
"deleteTitle": "Elimina",
|
"deleteTitle": "Elimina",
|
||||||
"displayName": "Nome visualizzato:",
|
"displayName": "Nome visualizzato:",
|
||||||
"download": "Scarica files",
|
"download": "Scarica files",
|
||||||
@@ -137,11 +141,11 @@
|
|||||||
"show": "Mostra",
|
"show": "Mostra",
|
||||||
"size": "Dimensione",
|
"size": "Dimensione",
|
||||||
"upload": "Carica",
|
"upload": "Carica",
|
||||||
"uploadFiles": "Uploading {files} files...",
|
"uploadFiles": "Inviando {files} file...",
|
||||||
"uploadMessage": "Seleziona un'opzione per il caricamento.",
|
"uploadMessage": "Seleziona un'opzione per il caricamento.",
|
||||||
"optionalPassword": "Optional password",
|
"optionalPassword": "Password opzionale",
|
||||||
"resolution": "Resolution",
|
"resolution": "Risoluzione",
|
||||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
"discardEditorChanges": "Sei sicuro di voler scartare le modifiche apportate?"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"images": "Immagini",
|
"images": "Immagini",
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Amministratore",
|
"administrator": "Amministratore",
|
||||||
"allowCommands": "Esegui comandi",
|
"allowCommands": "Esegui comandi",
|
||||||
@@ -170,14 +175,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}.",
|
"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!",
|
"commandsUpdated": "Comandi aggiornati!",
|
||||||
"createUserDir": "Crea automaticamente la home directory dell'utente quando lo aggiungi",
|
"createUserDir": "Crea automaticamente la home directory dell'utente quando lo aggiungi",
|
||||||
"minimumPasswordLength": "Minimum password length",
|
"minimumPasswordLength": "Lunghezza minima della password",
|
||||||
"tusUploads": "Chunked Uploads",
|
"tusUploads": "Tranci di invii",
|
||||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
"tusUploadsHelp": "File Browser supporta tranci di invii fornendo così la possibilità di inviare efficientemente i file anche su reti instabili.",
|
||||||
"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.",
|
"tusUploadsChunkSize": "Indica la dimensione massima di una richiesta (invii diretti saranno usati per piccoli invii). Puoi inserire un numero intero per indicare la dimensione in byte, oppure una stringa con l'unità di misura come in 10MB, 1GB, etc.",
|
||||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
"tusUploadsRetryCount": "Numero di tentativi da effettuare se un trancio di file fallisce.",
|
||||||
"userHomeBasePath": "Base path for user home directories",
|
"userHomeBasePath": "Percorso base per le cartelle utente",
|
||||||
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
"userScopeGenerationPlaceholder": "La portata verrà autogenerata",
|
||||||
"createUserHomeDirectory": "Create user home directory",
|
"createUserHomeDirectory": "Crea cartella utente",
|
||||||
"customStylesheet": "Foglio di stile personalizzato",
|
"customStylesheet": "Foglio di stile personalizzato",
|
||||||
"defaultUserDescription": "Queste sono le impostazioni predefinite per i nuovi utenti.",
|
"defaultUserDescription": "Queste sono le impostazioni predefinite per i nuovi utenti.",
|
||||||
"disableExternalLinks": "Disabilita link esterni (tranne per la documentazione)",
|
"disableExternalLinks": "Disabilita link esterni (tranne per la documentazione)",
|
||||||
@@ -217,14 +222,14 @@
|
|||||||
"rules": "Regole",
|
"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",
|
"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",
|
"scope": "Scope",
|
||||||
"setDateFormat": "Set exact date format",
|
"setDateFormat": "Fissa il formato di data esatto",
|
||||||
"settingsUpdated": "Impostazioni aggiornate!",
|
"settingsUpdated": "Impostazioni aggiornate!",
|
||||||
"shareDuration": "Durata della condivisione",
|
"shareDuration": "Durata della condivisione",
|
||||||
"shareManagement": "Gestione delle condivisioni",
|
"shareManagement": "Gestione delle condivisioni",
|
||||||
"shareDeleted": "Share deleted!",
|
"shareDeleted": "Percorso condiviso eliminato!",
|
||||||
"singleClick": "Usa un singolo click per aprire file e cartelle",
|
"singleClick": "Usa un singolo click per aprire file e cartelle",
|
||||||
"themes": {
|
"themes": {
|
||||||
"default": "System default",
|
"default": "Impostazione predefinita del sistema",
|
||||||
"dark": "Scuro",
|
"dark": "Scuro",
|
||||||
"light": "Chiaro",
|
"light": "Chiaro",
|
||||||
"title": "Tema"
|
"title": "Tema"
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "更新",
|
"update": "更新",
|
||||||
"upload": "アップロード",
|
"upload": "アップロード",
|
||||||
"openFile": "ファイルを開く",
|
"openFile": "ファイルを開く",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "ファイルのダウンロード",
|
"downloadFile": "ファイルのダウンロード",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "ログイン",
|
"submit": "ログイン",
|
||||||
"username": "ユーザー名",
|
"username": "ユーザー名",
|
||||||
"usernameTaken": "ユーザー名はすでに取得されています",
|
"usernameTaken": "ユーザー名はすでに取得されています",
|
||||||
"wrongCredentials": "ユーザー名またはパスワードが間違っています"
|
"wrongCredentials": "ユーザー名またはパスワードが間違っています",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "永久",
|
"permanent": "永久",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "動画"
|
"video": "動画"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "管理者",
|
"admin": "管理者",
|
||||||
"administrator": "管理者",
|
"administrator": "管理者",
|
||||||
"allowCommands": "コマンドの実行",
|
"allowCommands": "コマンドの実行",
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"clear": "지우기",
|
"clear": "지우기",
|
||||||
"close": "닫기",
|
"close": "닫기",
|
||||||
"continue": "Continue",
|
"continue": "계속",
|
||||||
"copy": "복사",
|
"copy": "복사",
|
||||||
"copyFile": "파일 복사",
|
"copyFile": "파일 복사",
|
||||||
"copyToClipboard": "클립보드 복사",
|
"copyToClipboard": "클립보드 복사",
|
||||||
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
"copyDownloadLinkToClipboard": "다운로드 링크 복사",
|
||||||
"create": "생성",
|
"create": "생성",
|
||||||
"delete": "삭제",
|
"delete": "삭제",
|
||||||
"download": "다운로드",
|
"download": "다운로드",
|
||||||
"file": "File",
|
"file": "파일",
|
||||||
"folder": "Folder",
|
"folder": "폴더",
|
||||||
"fullScreen": "Toggle full screen",
|
"fullScreen": "전체 화면 전환",
|
||||||
"hideDotfiles": "숨김파일(dotfile)을 표시 안함",
|
"hideDotfiles": "숨김파일(dotfile)을 표시 안함",
|
||||||
"info": "정보",
|
"info": "정보",
|
||||||
"more": "더보기",
|
"more": "더보기",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"ok": "확인",
|
"ok": "확인",
|
||||||
"permalink": "링크 얻기",
|
"permalink": "링크 얻기",
|
||||||
"previous": "이전",
|
"previous": "이전",
|
||||||
"preview": "Preview",
|
"preview": "미리보기",
|
||||||
"publish": "게시",
|
"publish": "게시",
|
||||||
"rename": "이름 바꾸기",
|
"rename": "이름 바꾸기",
|
||||||
"replace": "대체",
|
"replace": "대체",
|
||||||
@@ -36,13 +36,14 @@
|
|||||||
"selectMultiple": "다중 선택",
|
"selectMultiple": "다중 선택",
|
||||||
"share": "공유",
|
"share": "공유",
|
||||||
"shell": "쉘 전환",
|
"shell": "쉘 전환",
|
||||||
"submit": "Submit",
|
"submit": "제출",
|
||||||
"switchView": "보기 전환",
|
"switchView": "보기 전환",
|
||||||
"toggleSidebar": "사이드바 전환",
|
"toggleSidebar": "사이드바 전환",
|
||||||
"update": "업데이트",
|
"update": "업데이트",
|
||||||
"upload": "업로드",
|
"upload": "업로드",
|
||||||
"openFile": "Open file",
|
"openFile": "파일 열기",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "변경 사항 취소",
|
||||||
|
"saveChanges": "변경사항 저장"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "파일 다운로드",
|
"downloadFile": "파일 다운로드",
|
||||||
@@ -50,13 +51,13 @@
|
|||||||
"downloadSelected": "선택 항목 다운로드"
|
"downloadSelected": "선택 항목 다운로드"
|
||||||
},
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
"abortUpload": "Are you sure you wish to abort?"
|
"abortUpload": "업로드를 중단하시겠습니까?"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"forbidden": "접근 권한이 없습니다.",
|
"forbidden": "접근 권한이 없습니다.",
|
||||||
"internal": "오류가 발생하였습니다.",
|
"internal": "오류가 발생하였습니다.",
|
||||||
"notFound": "해당 경로를 찾을 수 없습니다.",
|
"notFound": "해당 경로를 찾을 수 없습니다.",
|
||||||
"connection": "The server can't be reached."
|
"connection": "서버에 연결할 수 없습니다."
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "본문",
|
"body": "본문",
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
"sortByLastModified": "수정시간순 정렬",
|
"sortByLastModified": "수정시간순 정렬",
|
||||||
"sortByName": "이름순",
|
"sortByName": "이름순",
|
||||||
"sortBySize": "크기순",
|
"sortBySize": "크기순",
|
||||||
"noPreview": "Preview is not available for this file."
|
"noPreview": "미리 보기가 지원되지 않는 파일 유형입니다."
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"click": "파일이나 디렉토리를 선택해주세요.",
|
"click": "파일이나 디렉토리를 선택해주세요.",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "로그인",
|
"submit": "로그인",
|
||||||
"username": "사용자 이름",
|
"username": "사용자 이름",
|
||||||
"usernameTaken": "사용자 이름이 존재합니다",
|
"usernameTaken": "사용자 이름이 존재합니다",
|
||||||
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오"
|
"wrongCredentials": "사용자 이름 또는 비밀번호를 확인하십시오",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "영구",
|
"permanent": "영구",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -109,8 +113,8 @@
|
|||||||
"currentlyNavigating": "현재 위치:",
|
"currentlyNavigating": "현재 위치:",
|
||||||
"deleteMessageMultiple": "{count} 개의 파일을 삭제하시겠습니까?",
|
"deleteMessageMultiple": "{count} 개의 파일을 삭제하시겠습니까?",
|
||||||
"deleteMessageSingle": "파일 혹은 디렉토리를 삭제하시겠습니까?",
|
"deleteMessageSingle": "파일 혹은 디렉토리를 삭제하시겠습니까?",
|
||||||
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
"deleteMessageShare": "이 공유({path})를 삭제하시겠습니까?",
|
||||||
"deleteUser": "Are you sure you want to delete this user?",
|
"deleteUser": "이 계정을 삭제하시겠습니까?",
|
||||||
"deleteTitle": "파일 삭제",
|
"deleteTitle": "파일 삭제",
|
||||||
"displayName": "게시 이름:",
|
"displayName": "게시 이름:",
|
||||||
"download": "파일 다운로드",
|
"download": "파일 다운로드",
|
||||||
@@ -137,11 +141,11 @@
|
|||||||
"show": "보기",
|
"show": "보기",
|
||||||
"size": "크기",
|
"size": "크기",
|
||||||
"upload": "업로드",
|
"upload": "업로드",
|
||||||
"uploadFiles": "Uploading {files} files...",
|
"uploadFiles": "{files}개의 파일 업로드 중...",
|
||||||
"uploadMessage": "업로드 옵션을 선택하세요.",
|
"uploadMessage": "업로드 옵션을 선택하세요.",
|
||||||
"optionalPassword": "Optional password",
|
"optionalPassword": "비밀번호 (선택)",
|
||||||
"resolution": "Resolution",
|
"resolution": "해상도",
|
||||||
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
"discardEditorChanges": "변경 사항을 취소하시겠습니까?"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"images": "이미지",
|
"images": "이미지",
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "비디오"
|
"video": "비디오"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "관리자",
|
"admin": "관리자",
|
||||||
"administrator": "관리자",
|
"administrator": "관리자",
|
||||||
"allowCommands": "명령 실행",
|
"allowCommands": "명령 실행",
|
||||||
@@ -170,14 +175,14 @@
|
|||||||
"commandRunnerHelp": "이벤트에 해당하는 명령을 설정하세요. 줄당 1개의 명령을 적으세요. 환경 변수{0} 와 {1}이 사용가능하며, {0} 은 {1}에 상대 경로 입니다. 자세한 사항은 {2} 를 참조하세요.",
|
"commandRunnerHelp": "이벤트에 해당하는 명령을 설정하세요. 줄당 1개의 명령을 적으세요. 환경 변수{0} 와 {1}이 사용가능하며, {0} 은 {1}에 상대 경로 입니다. 자세한 사항은 {2} 를 참조하세요.",
|
||||||
"commandsUpdated": "명령 수정됨!",
|
"commandsUpdated": "명령 수정됨!",
|
||||||
"createUserDir": "Auto create user home dir while adding new user",
|
"createUserDir": "Auto create user home dir while adding new user",
|
||||||
"minimumPasswordLength": "Minimum password length",
|
"minimumPasswordLength": "최소 비밀번호 길이",
|
||||||
"tusUploads": "Chunked Uploads",
|
"tusUploads": "분할 업로드",
|
||||||
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
"tusUploadsHelp": "File Browser는 불안정한 네트워크에서도 효율적이고 신뢰성 있는 분할 업로드를 지원합니다.",
|
||||||
"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.",
|
"tusUploadsChunkSize": "업로드 요청의 최대 크기 (예: 10MB, 1GB)",
|
||||||
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
"tusUploadsRetryCount": "업로드 실패 시 재시도 횟수",
|
||||||
"userHomeBasePath": "Base path for user home directories",
|
"userHomeBasePath": "사용자 홈 폴더 기본 경로",
|
||||||
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
"userScopeGenerationPlaceholder": "범위는 자동으로 생성됩니다.",
|
||||||
"createUserHomeDirectory": "Create user home directory",
|
"createUserHomeDirectory": "사용자 홈 폴더 생성",
|
||||||
"customStylesheet": "커스텀 스타일시트",
|
"customStylesheet": "커스텀 스타일시트",
|
||||||
"defaultUserDescription": "아래 사항은 신규 사용자들에 대한 기본 설정입니다.",
|
"defaultUserDescription": "아래 사항은 신규 사용자들에 대한 기본 설정입니다.",
|
||||||
"disableExternalLinks": "외부 링크 감추기",
|
"disableExternalLinks": "외부 링크 감추기",
|
||||||
@@ -217,14 +222,14 @@
|
|||||||
"rules": "룰",
|
"rules": "룰",
|
||||||
"rulesHelp": "사용자별로 규칙을 허용/방지를 지정할 수 있습니다. 방지된 파일은 보이지 않고 사용자들은 접근할 수 없습니다. 사용자의 접근 허용 범위와 관련해 정규표현식(regex)과 경로를 지원합니다.\n",
|
"rulesHelp": "사용자별로 규칙을 허용/방지를 지정할 수 있습니다. 방지된 파일은 보이지 않고 사용자들은 접근할 수 없습니다. 사용자의 접근 허용 범위와 관련해 정규표현식(regex)과 경로를 지원합니다.\n",
|
||||||
"scope": "범위",
|
"scope": "범위",
|
||||||
"setDateFormat": "Set exact date format",
|
"setDateFormat": "날짜 형식 설정",
|
||||||
"settingsUpdated": "설정 수정됨!",
|
"settingsUpdated": "설정 수정됨!",
|
||||||
"shareDuration": "공유 기간",
|
"shareDuration": "공유 기간",
|
||||||
"shareManagement": "공유 내역 관리",
|
"shareManagement": "공유 내역 관리",
|
||||||
"shareDeleted": "Share deleted!",
|
"shareDeleted": "공유 삭제됨!",
|
||||||
"singleClick": "한번 클릭으로 파일과 폴더를 열도록 합니다.",
|
"singleClick": "한번 클릭으로 파일과 폴더를 열도록 합니다.",
|
||||||
"themes": {
|
"themes": {
|
||||||
"default": "System default",
|
"default": "시스템 기본값",
|
||||||
"dark": "다크테마",
|
"dark": "다크테마",
|
||||||
"light": "라이트테마",
|
"light": "라이트테마",
|
||||||
"title": "테마"
|
"title": "테마"
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"update": "Updaten",
|
"update": "Updaten",
|
||||||
"upload": "Uploaden",
|
"upload": "Uploaden",
|
||||||
"openFile": "Open file",
|
"openFile": "Open file",
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard",
|
||||||
|
"saveChanges": "Save changes"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Bestand downloaden",
|
"downloadFile": "Bestand downloaden",
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
"submit": "Log in",
|
"submit": "Log in",
|
||||||
"username": "Gebruikersnaam",
|
"username": "Gebruikersnaam",
|
||||||
"usernameTaken": "Gebruikersnaam reeds in gebruik",
|
"usernameTaken": "Gebruikersnaam reeds in gebruik",
|
||||||
"wrongCredentials": "Verkeerde inloggegevens"
|
"wrongCredentials": "Verkeerde inloggegevens",
|
||||||
|
"logout_reasons": {
|
||||||
|
"inactivity": "You have been logged out due to inactivity."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"prompts": {
|
"prompts": {
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
"video": "Video"
|
"video": "Video"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"aceEditorTheme": "Ace editor theme",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "Commando's uitvoeren",
|
"allowCommands": "Commando's uitvoeren",
|
||||||
|
|||||||