Compare commits

...

11 Commits

Author SHA1 Message Date
Henrique Dias
70d59ec03e
chore(release): 2.34.1 2025-06-29 11:28:57 +02:00
Henrique Dias
bf37f88c32
fix: passthrough the minimum password length (#5236) 2025-06-29 11:28:32 +02:00
Foxy Hunter
7354eb6cf9
fix: exclude to-be-moved folder from move dialog (#5235) 2025-06-29 11:23:06 +02:00
Henrique Dias
10684e5390
docs: bring the maintenance warning higher in the page 2025-06-29 10:13:39 +02:00
Henrique Dias
58fe817349
docs: add link to contributing and license in readme 2025-06-29 10:13:01 +02:00
Henrique Dias
d8472e767b
chore(release): 2.34.0 2025-06-29 09:54:35 +02:00
Henrique Dias
8700cb30ff
chore: reuse docker flags 2025-06-29 09:51:44 +02:00
manx98
93c4b2e03c
fix: abort ongoing requests when changing pages (#3927) 2025-06-29 09:38:03 +02:00
Henrique Dias
2d1a82b73f
docs: improvements to building and docs (#5234) 2025-06-29 09:28:39 +02:00
dependabot[bot]
5a07291306
build(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /tools (#5228)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-29 09:20:44 +02:00
transifex-integration[bot]
09f679fae4
feat: Translate frontend/src/i18n/en.json in fa (#5233)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-29 09:17:05 +02:00
50 changed files with 100320 additions and 226 deletions

View File

@ -1,7 +1,10 @@
name: Build Site Image name: Build Site
on: on:
pull_request: pull_request:
paths:
- 'www'
- '*.md'
jobs: jobs:
build: build:
@ -13,11 +16,5 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Build - name: Build site
uses: docker/build-push-action@v4 run: make site
with:
context: .
file: ./Dockerfile.site
platforms: linux/amd64,linux/arm64
push: false

View File

@ -1,4 +1,4 @@
name: Build and Push Site Image name: Build and Deploy Site
on: on:
push: push:
@ -6,39 +6,6 @@ on:
- master - master
jobs: 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: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile.site
push: true
platforms: linux/amd64,linux/arm64
tags: |
filebrowser/site:latest
ghcr.io/filebrowser/site:latest
deploy: deploy:
permissions: permissions:
contents: read contents: read
@ -61,5 +28,5 @@ jobs:
with: with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site/public/site --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }} command: pages deploy www/public --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }} gitHubToken: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@ -42,4 +42,3 @@ build/
default.nix default.nix
Dockerfile.dev Dockerfile.dev
site/public/

View File

@ -2,6 +2,46 @@
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 [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.34.1](https://github.com/filebrowser/filebrowser/compare/v2.34.0...v2.34.1) (2025-06-29)
### Bug Fixes
* exclude to-be-moved folder from move dialog ([#5235](https://github.com/filebrowser/filebrowser/issues/5235)) ([7354eb6](https://github.com/filebrowser/filebrowser/commit/7354eb6cf966244141277c2808988855c004f908))
* passthrough the minimum password length ([#5236](https://github.com/filebrowser/filebrowser/issues/5236)) ([bf37f88](https://github.com/filebrowser/filebrowser/commit/bf37f88c32222ad9c186482bb97338a9c9b4a93c))
## [2.34.0](https://github.com/filebrowser/filebrowser/compare/v2.33.10...v2.34.0) (2025-06-29)
### Features
* Translate frontend/src/i18n/en.json in fa ([0acd69c](https://github.com/filebrowser/filebrowser/commit/0acd69c537ce2909ff62c4bb6980982524ece221))
* Translate frontend/src/i18n/en.json in fa ([#5233](https://github.com/filebrowser/filebrowser/issues/5233)) ([09f679f](https://github.com/filebrowser/filebrowser/commit/09f679fae43398f5b87d21acc9d974d4d053392f))
* update translations for project File Browser ([#5226](https://github.com/filebrowser/filebrowser/issues/5226)) ([a5ea2a2](https://github.com/filebrowser/filebrowser/commit/a5ea2a266bef619d1c4322266d1aa7d397d2c856))
### Bug Fixes
* abort ongoing requests when changing pages ([#3927](https://github.com/filebrowser/filebrowser/issues/3927)) ([93c4b2e](https://github.com/filebrowser/filebrowser/commit/93c4b2e03c5176da01a7e00a03c03ffcce279bc8))
* add configurable minimum password length ([#5225](https://github.com/filebrowser/filebrowser/issues/5225)) ([464b644](https://github.com/filebrowser/filebrowser/commit/464b644adf22a2178414a6f1e4fa286276de81d2))
* do not expose the name of the root directory ([#5224](https://github.com/filebrowser/filebrowser/issues/5224)) ([0892559](https://github.com/filebrowser/filebrowser/commit/089255997a653c284cd4249990b58bed00086c61))
* Graceful shutdown ([8230eb7](https://github.com/filebrowser/filebrowser/commit/8230eb7ab51ccbd00b03f5b9d6964fa4aae331d4))
### Reverts
* Revert "docs: change cloudflare environment (#5231)" (#5232) ([9e273cd](https://github.com/filebrowser/filebrowser/commit/9e273cd9475d57b9500034e8b341ff8b620bcab8)), closes [#5231](https://github.com/filebrowser/filebrowser/issues/5231) [#5232](https://github.com/filebrowser/filebrowser/issues/5232)
### Build
* add an arm64 target for the site image ([#5229](https://github.com/filebrowser/filebrowser/issues/5229)) ([f5e531c](https://github.com/filebrowser/filebrowser/commit/f5e531c8ae0b9b18717e184856ace0ce19beef82))
* bump golangci-lint to 2.1.6 ([1d494ff](https://github.com/filebrowser/filebrowser/commit/1d494ff3159ef939cfb4980ccde6f27df3e738b5))
* **deps:** bump brace-expansion from 1.1.11 to 1.1.12 in /tools ([#5228](https://github.com/filebrowser/filebrowser/issues/5228)) ([5a07291](https://github.com/filebrowser/filebrowser/commit/5a072913062a6b2b0e5c74a02ca7710218ed3e5e))
* **deps:** bump github.com/go-viper/mapstructure/v2 ([f32f273](https://github.com/filebrowser/filebrowser/commit/f32f27383d1fafa074f038cc873bd37b7f20ee27))
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([5331969](https://github.com/filebrowser/filebrowser/commit/5331969163f5ae1fd2389f665059fc9e4a98db15))
* publish docs to cloudflare pages ([#5230](https://github.com/filebrowser/filebrowser/issues/5230)) ([8861933](https://github.com/filebrowser/filebrowser/commit/8861933cf845b104e072f35e5f37d7c26097c9dc))
### [2.33.10](https://github.com/filebrowser/filebrowser/compare/v2.33.9...v2.33.10) (2025-06-26) ### [2.33.10](https://github.com/filebrowser/filebrowser/compare/v2.33.9...v2.33.10) (2025-06-26)

View File

@ -1,6 +1,6 @@
# Contributing # Contributing
If you're interested in contributing to this project, this is the best place to start. Before contributing to this project, please take a bit of time to read our [Code of Conduct](code-of-conduct.md). Also, note that this project is open-source and licensed under [Apache License 2.0](../LICENSE). If you're interested in contributing to this project, this is the best place to start. Before contributing to this project, please take a bit of time to read our [Code of Conduct](code-of-conduct.md). Also, note that this project is open-source and licensed under [Apache License 2.0](LICENSE).
## Project Structure ## Project Structure

View File

@ -1,22 +0,0 @@
FROM squidfunk/mkdocs-material as build
WORKDIR /build
COPY site/requirements.txt /build/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY LICENSE /build/docs/LICENSE
COPY site/ /build/
COPY docs/ /build/docs/docs
COPY README.md /build/docs/index.md
RUN mkdocs build
FROM ghcr.io/umputun/reproxy
# enables automatic changelog generation by tools like Dependabot
LABEL org.opencontainers.image.source="https://github.com/filebrowser/filebrowser"
COPY --from=build /build/site /srv/site
EXPOSE 8080
USER app
ENTRYPOINT ["/srv/reproxy", "--assets.location=/srv/site", "--assets.cache=30d", "--assets.cache=text/html:30s"]

View File

@ -1,15 +0,0 @@
FROM squidfunk/mkdocs-material
# Install inotify-tools for watching file changes
RUN apk add --no-cache inotify-tools
WORKDIR /build
COPY site/ /build/
RUN pip install --no-cache-dir -r requirements.txt
# Expose the port for mkdocs serve
EXPOSE 8000
# The entrypoint will run the initial setup and then start the server.
ENTRYPOINT ["mkdocs", "serve", "-a", "0.0.0.0:8000", "--dirtyreload"]

View File

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2018 File Browser contributors Copyright 2018 File Browser Contributors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -3,6 +3,14 @@ include tools.mk
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)" 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: ## Build:
.PHONY: build .PHONY: build
@ -55,18 +63,14 @@ bump-version: $(standard-version) ## Bump app version
.PHONY: site .PHONY: site
site: ## Build site site: ## Build site
@rm -rf site/public/site* @rm -rf www/public
@docker rm -f spot-site docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
docker build -f Dockerfile.site --progress=plain -t filebrowser.site . docker run --rm $(SITE_DOCKER_FLAGS) filebrowser.site build -d "public"
docker run -d --name=filebrowser-site filebrowser.site
sleep 3
docker cp "filebrowser-site":/srv/site/ site/public
docker rm -f filebrowser-site
.PHONY: site-serve .PHONY: site-serve
site-serve: ## Serve site for development site-serve: ## Serve site for development
docker build -f Dockerfile.site.dev -t filebrowser.site.dev . docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
docker run --rm -it -p 8000:8000 -v $(CURDIR)/docs:/build/docs/docs -v $(CURDIR)/README.md:/build/docs/index.md filebrowser.site.dev docker run --rm -it -p 8000:8000 $(SITE_DOCKER_FLAGS) filebrowser.site
## Help: ## Help:
help: ## Show this help help: ## Show this help

View File

@ -1,17 +1,20 @@
#
<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/logo/master/banner.png" width="550"/>
</p> </p>
![Preview](https://user-images.githubusercontent.com/5447088/50716739-ebd26700-107a-11e9-9817-14230c53efd2.gif)
[![Build](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml/badge.svg)](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml) [![Build](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml/badge.svg)](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser?style=flat-square)](https://goreportcard.com/report/github.com/filebrowser/filebrowser) [![Go Report Card](https://goreportcard.com/badge/github.com/filebrowser/filebrowser)](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/filebrowser/filebrowser) [![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/filebrowser/filebrowser)
[![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg?style=flat-square)](https://github.com/filebrowser/filebrowser/releases/latest) [![Version](https://img.shields.io/github/release/filebrowser/filebrowser.svg)](https://github.com/filebrowser/filebrowser/releases/latest)
[![Chat IRC](https://img.shields.io/badge/freenode-%23filebrowser-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23filebrowser) [![Chat IRC](https://img.shields.io/badge/freenode-%23filebrowser-blue.svg)](http://webchat.freenode.net/?channels=%23filebrowser)
filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app. File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview and edit your files. It is a **create-your-own-cloud**-kind of software where you can just install it on your server, direct it to a path and access your files through a nice web interface.
## Documentation
Documentation on how to install, configure, and contribute to this project is hosted at [filebrowser.org](https://filebrowser.org).
## Project Status
> [!WARNING] > [!WARNING]
> >
@ -26,27 +29,10 @@ filebrowser provides a file managing interface within a specified directory and
[issues]: https://github.com/filebrowser/filebrowser/issues [issues]: https://github.com/filebrowser/filebrowser/issues
[discussions]: https://github.com/filebrowser/filebrowser/discussions [discussions]: https://github.com/filebrowser/filebrowser/discussions
## Features
File Browser is a **create-your-own-cloud-kind** of software where you can install it on a server, direct it to a path and then access your files through a nice web interface. You have many available features!
| Easy Login System | Sleek Interface | User Management |
| :----------------------: | :----------------------: | :----------------------: |
| ![](docs/assets/1.jpg) | ![](docs/assets/2.jpg) | ![](docs/assets/3.jpg) |
| File Editing | Custom Commands | Customization |
| :----------------------: | :----------------------: | :----------------------: |
| ![](docs/assets/4.jpg) | ![](docs/assets/5.jpg) | ![](docs/assets/6.jpg) |
## Install
For information on how to install File Browser, please check [installation](docs/installation.md).
## Configuration
For information on how to configure File Browser, please check [configuration](docs/configuration.md).
## Contributing ## Contributing
For information on how to contribute to the project, including how translations are managed, please check [contributing](docs/contributing.md). Contributions are always welcome. To start contributing to this project, read our [guidelines](CONTRIBUTING.md) first.
## License
[Apache License 2.0](LICENSE) © File Browser Contributors

View File

@ -150,7 +150,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
} }
if u == nil { if u == nil {
pass, err := users.HashAndValidatePwd(a.Cred.Password, a.Settings.MinimumPasswordLength) pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -186,7 +186,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
// update the password when it doesn't match the current // update the password when it doesn't match the current
if p { if p {
pass, err := users.HashAndValidatePwd(a.Cred.Password, a.Settings.MinimumPasswordLength) pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -35,7 +35,7 @@ func (a ProxyAuth) createUser(usr users.Store, setting *settings.Settings, srv *
} }
var hashedRandomPassword string var hashedRandomPassword string
hashedRandomPassword, err = users.HashAndValidatePwd(pwd, setting.MinimumPasswordLength) hashedRandomPassword, err = users.ValidateAndHashPwd(pwd, setting.MinimumPasswordLength)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -432,7 +432,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
log.Println("Randomly generated password for user 'admin':", pwd) log.Println("Randomly generated password for user 'admin':", pwd)
password, err = users.HashAndValidatePwd(pwd, set.MinimumPasswordLength) password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
checkErr(err) checkErr(err)
} }

View File

@ -21,7 +21,7 @@ var usersAddCmd = &cobra.Command{
checkErr(err) checkErr(err)
getUserDefaults(cmd.Flags(), &s.Defaults, false) getUserDefaults(cmd.Flags(), &s.Defaults, false)
password, err := users.HashAndValidatePwd(args[1], s.MinimumPasswordLength) password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
checkErr(err) checkErr(err)
user := &users.User{ user := &users.User{

View File

@ -66,7 +66,7 @@ options you want to change.`,
} }
if password != "" { if password != "" {
user.Password, err = users.HashAndValidatePwd(password, s.MinimumPasswordLength) user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
checkErr(err) checkErr(err)
} }

View File

@ -1,13 +1,16 @@
package errors package errors
import "errors" import (
"errors"
"fmt"
)
var ( var (
ErrEmptyKey = errors.New("empty key") ErrEmptyKey = errors.New("empty key")
ErrExist = errors.New("the resource already exists") ErrExist = errors.New("the resource already exists")
ErrNotExist = errors.New("the resource does not exist") ErrNotExist = errors.New("the resource does not exist")
ErrEmptyPassword = errors.New("password is empty") ErrEmptyPassword = errors.New("password is empty")
ErrShortPassword = errors.New("password is too short") ErrEasyPassword = errors.New("password is too easy")
ErrEmptyUsername = errors.New("username is empty") ErrEmptyUsername = errors.New("username is empty")
ErrEmptyRequest = errors.New("empty request") ErrEmptyRequest = errors.New("empty request")
ErrScopeIsRelative = errors.New("scope is a relative path") ErrScopeIsRelative = errors.New("scope is a relative path")
@ -20,3 +23,11 @@ var (
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")
) )
type ErrShortPassword struct {
MinimumLength uint
}
func (e ErrShortPassword) Error() string {
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
}

View File

@ -2,14 +2,22 @@ import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import { upload as postTus, useTus } from "./tus"; import { upload as postTus, useTus } from "./tus";
import { createURL, fetchURL, removePrefix } from "./utils"; import { createURL, fetchURL, removePrefix, StatusError } from "./utils";
export async function fetch(url: string) { export async function fetch(url: string, signal?: AbortSignal) {
url = removePrefix(url); url = removePrefix(url);
const res = await fetchURL(`/api/resources${url}`, { signal });
const res = await fetchURL(`/api/resources${url}`, {}); let data: Resource;
try {
const data = (await res.json()) as Resource; data = (await res.json()) as Resource;
} catch (e) {
// Check if the error is an intentional cancellation
if (e instanceof Error && e.name === "AbortError") {
throw new StatusError("000 No connection", 0, true);
}
throw e;
}
data.url = `/files${url}`; data.url = `/files${url}`;
if (data.isDir) { if (data.isDir) {
@ -205,10 +213,18 @@ export function getSubtitlesURL(file: ResourceItem) {
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params)); return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
} }
export async function usage(url: string) { export async function usage(url: string, signal: AbortSignal) {
url = removePrefix(url); url = removePrefix(url);
const res = await fetchURL(`/api/usage${url}`, {}); const res = await fetchURL(`/api/usage${url}`, { signal });
return await res.json(); try {
return await res.json();
} catch (e) {
// Check if the error is an intentional cancellation
if (e instanceof Error && e.name == "AbortError") {
throw new StatusError("000 No connection", 0, true);
}
throw e;
}
} }

View File

@ -6,7 +6,8 @@ import { encodePath } from "@/utils/url";
export class StatusError extends Error { export class StatusError extends Error {
constructor( constructor(
message: any, message: any,
public status?: number public status?: number,
public is_canceled?: boolean
) { ) {
super(message); super(message);
this.name = "StatusError"; this.name = "StatusError";
@ -33,7 +34,11 @@ export async function fetchURL(
}, },
...rest, ...rest,
}); });
} catch { } catch (e) {
// Check if the error is an intentional cancellation
if (e instanceof Error && e.name === "AbortError") {
throw new StatusError("000 No connection", 0, true);
}
throw new StatusError("000 No connection", 0); throw new StatusError("000 No connection", 0);
} }

View File

@ -129,6 +129,7 @@ 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 };
@ -136,7 +137,7 @@ export default {
name: "sidebar", name: "sidebar",
setup() { setup() {
const usage = reactive(USAGE_DEFAULT); const usage = reactive(USAGE_DEFAULT);
return { usage }; return { usage, usageAbortController: new AbortController() };
}, },
components: { components: {
ProgressBar, ProgressBar,
@ -157,6 +158,9 @@ export default {
}, },
methods: { methods: {
...mapActions(useLayoutStore, ["closeHovers", "showHover"]), ...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
abortOngoingFetchUsage() {
this.usageAbortController.abort();
},
async fetchUsage() { async fetchUsage() {
const path = this.$route.path.endsWith("/") const path = this.$route.path.endsWith("/")
? this.$route.path ? this.$route.path
@ -166,13 +170,18 @@ export default {
return Object.assign(this.usage, usageStats); return Object.assign(this.usage, usageStats);
} }
try { try {
const usage = await api.usage(path); this.abortOngoingFetchUsage();
this.usageAbortController = new AbortController();
const usage = await api.usage(path, this.usageAbortController.signal);
usageStats = { usageStats = {
used: prettyBytes(usage.used, { binary: true }), used: prettyBytes(usage.used, { binary: true }),
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) { } catch (error) {
if (error instanceof StatusError && error.is_canceled) {
return;
}
this.$showError(error); this.$showError(error);
} }
return Object.assign(this.usage, usageStats); return Object.assign(this.usage, usageStats);
@ -200,5 +209,8 @@ export default {
immediate: true, immediate: true,
}, },
}, },
unmounted() {
this.abortOngoingFetchUsage();
},
}; };
</script> </script>

View File

@ -31,9 +31,16 @@ import { useFileStore } from "@/stores/file";
import url from "@/utils/url"; import url from "@/utils/url";
import { files } from "@/api"; import { files } from "@/api";
import { StatusError } from "@/api/utils.js";
export default { export default {
name: "file-list", name: "file-list",
props: {
exclude: {
type: Array,
default: () => [],
},
},
data: function () { data: function () {
return { return {
items: [], items: [],
@ -43,6 +50,7 @@ export default {
}, },
selected: null, selected: null,
current: window.location.pathname, current: window.location.pathname,
nextAbortController: new AbortController(),
}; };
}, },
inject: ["$showError"], inject: ["$showError"],
@ -56,7 +64,13 @@ export default {
mounted() { mounted() {
this.fillOptions(this.req); this.fillOptions(this.req);
}, },
unmounted() {
this.abortOngoingNext();
},
methods: { methods: {
abortOngoingNext() {
this.nextAbortController.abort();
},
fillOptions(req) { fillOptions(req) {
// Sets the current path and resets // Sets the current path and resets
// the current items. // the current items.
@ -82,6 +96,7 @@ export default {
// move options. // move options.
for (const item of req.items) { for (const item of req.items) {
if (!item.isDir) continue; if (!item.isDir) continue;
if (this.exclude?.includes(item.url)) continue;
this.items.push({ this.items.push({
name: item.name, name: item.name,
@ -94,8 +109,17 @@ export default {
// just clicked in and fill the options with its // just clicked in and fill the options with its
// content. // content.
const uri = event.currentTarget.dataset.url; const uri = event.currentTarget.dataset.url;
this.abortOngoingNext();
files.fetch(uri).then(this.fillOptions).catch(this.$showError); this.nextAbortController = new AbortController();
files
.fetch(uri, this.nextAbortController.signal)
.then(this.fillOptions)
.catch((e) => {
if (e instanceof StatusError && e.is_canceled) {
return;
}
this.$showError(e);
});
}, },
touchstart(event) { touchstart(event) {
const url = event.currentTarget.dataset.url; const url = event.currentTarget.dataset.url;

View File

@ -8,6 +8,7 @@
<file-list <file-list
ref="fileList" ref="fileList"
@update:selected="(val) => (dest = val)" @update:selected="(val) => (dest = val)"
:exclude="excludedFolders"
tabindex="1" tabindex="1"
/> />
</div> </div>
@ -76,6 +77,11 @@ export default {
computed: { computed: {
...mapState(useFileStore, ["req", "selected"]), ...mapState(useFileStore, ["req", "selected"]),
...mapState(useAuthStore, ["user"]), ...mapState(useAuthStore, ["user"]),
excludedFolders() {
return this.selected
.filter((idx) => this.req.items[idx].isDir)
.map((idx) => this.req.items[idx].url);
},
}, },
methods: { methods: {
...mapActions(useLayoutStore, ["showHover", "closeHovers"]), ...mapActions(useLayoutStore, ["showHover", "closeHovers"]),

View File

@ -170,7 +170,7 @@
"commandRunnerHelp": "در اینجا می‌توانید دستوراتی را که در رویدادهای نامگذاری شده اجرا می‌شوند، تنظیم کنید. باید در هر خط یکی را بنویسید. متغیرهای محیطی {0} و {1} در دسترس خواهند بود و {0} نسبت به {1} هستند. برای اطلاعات بیشتر در مورد این ویژگی و متغیرهای محیطی موجود، لطفاً {2} را مطالعه کنید.", "commandRunnerHelp": "در اینجا می‌توانید دستوراتی را که در رویدادهای نامگذاری شده اجرا می‌شوند، تنظیم کنید. باید در هر خط یکی را بنویسید. متغیرهای محیطی {0} و {1} در دسترس خواهند بود و {0} نسبت به {1} هستند. برای اطلاعات بیشتر در مورد این ویژگی و متغیرهای محیطی موجود، لطفاً {2} را مطالعه کنید.",
"commandsUpdated": "دستورات ویرایش شد!", "commandsUpdated": "دستورات ویرایش شد!",
"createUserDir": "ایجاد خودکار پوشه برای هر کاربر هنگام اضافه کردن کاربر جدید", "createUserDir": "ایجاد خودکار پوشه برای هر کاربر هنگام اضافه کردن کاربر جدید",
"minimumPasswordLength": "Minimum password length", "minimumPasswordLength": "حداقل طول رمز عبور",
"tusUploads": "آپلودهای بخش بخش شده", "tusUploads": "آپلودهای بخش بخش شده",
"tusUploadsHelp": "مرورگر فایل از آپلود فایل بخش بخش شده پشتیبانی می‌کند و امکان ایجاد آپلودهای فایل کارآمد، قابل اعتماد، قابل از سرگیری و بخش بخش شده را حتی در شبکه‌های غیرقابل اعتماد فراهم می‌کند.", "tusUploadsHelp": "مرورگر فایل از آپلود فایل بخش بخش شده پشتیبانی می‌کند و امکان ایجاد آپلودهای فایل کارآمد، قابل اعتماد، قابل از سرگیری و بخش بخش شده را حتی در شبکه‌های غیرقابل اعتماد فراهم می‌کند.",
"tusUploadsChunkSize": "حداکثر اندازه یک درخواست را نشان می‌دهد (برای آپلودهای کوچک‌تر از آپلود مستقیم استفاده می‌شود). می‌توانید یک عدد صحیح ساده که نشان‌دهنده اندازه بایت است یا یک رشته مانند ۱۰ مگابایت، ۱ گیگابایت و غیره وارد کنید.", "tusUploadsChunkSize": "حداکثر اندازه یک درخواست را نشان می‌دهد (برای آپلودهای کوچک‌تر از آپلود مستقیم استفاده می‌شود). می‌توانید یک عدد صحیح ساده که نشان‌دهنده اندازه بایت است یا یک رشته مانند ۱۰ مگابایت، ۱ گیگابایت و غیره وارد کنید.",

View File

@ -10,6 +10,7 @@ interface ApiOpts {
method?: ApiMethod; method?: ApiMethod;
headers?: object; headers?: object;
body?: any; body?: any;
signal?: AbortSignal;
} }
interface TusSettings { interface TusSettings {

View File

@ -61,9 +61,7 @@ const route = useRoute();
const { t } = useI18n({}); const { t } = useI18n({});
const clean = (path: string) => { let fetchDataController = new AbortController();
return path.endsWith("/") ? path.slice(0, -1) : path;
};
const error = ref<StatusError | null>(null); const error = ref<StatusError | null>(null);
@ -101,6 +99,7 @@ onUnmounted(() => {
layoutStore.toggleShell(); layoutStore.toggleShell();
} }
fileStore.updateRequest(null); fileStore.updateRequest(null);
fetchDataController.abort();
}); });
watch(route, (to, from) => { watch(route, (to, from) => {
@ -142,20 +141,21 @@ const fetchData = async () => {
let url = route.path; let url = route.path;
if (url === "") url = "/"; if (url === "") url = "/";
if (url[0] !== "/") url = "/" + url; if (url[0] !== "/") url = "/" + url;
// Cancel the ongoing request
fetchDataController.abort();
fetchDataController = new AbortController();
try { try {
const res = await api.fetch(url); const res = await api.fetch(url, fetchDataController.signal);
if (clean(res.path) !== clean(`/${[...route.params.path].join("/")}`)) {
throw new Error("Data Mismatch!");
}
fileStore.updateRequest(res); fileStore.updateRequest(res);
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`; document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
layoutStore.loading = false;
} catch (err) { } catch (err) {
if (err instanceof StatusError && err.is_canceled) {
return;
}
if (err instanceof Error) { if (err instanceof Error) {
error.value = err; error.value = err;
} }
} finally {
layoutStore.loading = false; layoutStore.loading = false;
} }
}; };

View File

@ -151,9 +151,9 @@ var signupHandler = func(_ http.ResponseWriter, r *http.Request, d *data) (int,
d.settings.Defaults.Apply(user) d.settings.Defaults.Apply(user)
pwd, err := users.HashAndValidatePwd(info.Password, d.settings.MinimumPasswordLength) pwd, err := users.ValidateAndHashPwd(info.Password, d.settings.MinimumPasswordLength)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusBadRequest, err
} }
user.Password = pwd user.Password = pwd

View File

@ -125,13 +125,9 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
return http.StatusBadRequest, fbErrors.ErrEmptyPassword return http.StatusBadRequest, fbErrors.ErrEmptyPassword
} }
if len(req.Data.Password) < int(d.settings.MinimumPasswordLength) { req.Data.Password, err = users.ValidateAndHashPwd(req.Data.Password, d.settings.MinimumPasswordLength)
return http.StatusBadRequest, fbErrors.ErrShortPassword
}
req.Data.Password, err = users.HashAndValidatePwd(req.Data.Password, d.settings.MinimumPasswordLength)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusBadRequest, err
} }
userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root) userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root)
@ -167,17 +163,19 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
} }
if req.Data.Password != "" { if req.Data.Password != "" {
req.Data.Password, err = users.HashAndValidatePwd(req.Data.Password, d.settings.MinimumPasswordLength) req.Data.Password, err = users.ValidateAndHashPwd(req.Data.Password, d.settings.MinimumPasswordLength)
if err != nil {
return http.StatusBadRequest, err
}
} else { } else {
var suser *users.User var suser *users.User
suser, err = d.store.Users.Get(d.server.Root, d.raw.(uint)) suser, err = d.store.Users.Get(d.server.Root, d.raw.(uint))
if err != nil {
return http.StatusInternalServerError, err
}
req.Data.Password = suser.Password req.Data.Password = suser.Password
} }
if err != nil {
return http.StatusInternalServerError, err
}
req.Which = []string{} req.Which = []string{}
} }
@ -190,13 +188,9 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
if len(req.Data.Password) < int(d.settings.MinimumPasswordLength) { req.Data.Password, err = users.ValidateAndHashPwd(req.Data.Password, d.settings.MinimumPasswordLength)
return http.StatusBadRequest, fbErrors.ErrShortPassword
}
req.Data.Password, err = users.HashAndValidatePwd(req.Data.Password, d.settings.MinimumPasswordLength)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusBadRequest, err
} }
} }

View File

@ -1,17 +0,0 @@
#!/bin/sh
# Initial setup
cp /tmp/README.md /build/docs/index.md
sed -i 's|site/docs/||g' /build/docs/index.md
# Start mkdocs in the background
mkdocs serve -a 0.0.0.0:8000 --dirtyreload &
# Watch for changes in README.md
while true; do
inotifywait -e modify /tmp/README.md
echo "README.md changed. Updating index.md..."
cp /tmp/README.md /build/docs/index.md
sed -i 's|site/docs/||g' /build/docs/index.md
done

View File

View File

@ -246,9 +246,9 @@ balanced-match@^1.0.0:
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.12"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
dependencies: dependencies:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"

26
users/assets.go Normal file
View File

@ -0,0 +1,26 @@
package users
import (
"embed"
"strings"
)
//go:embed assets
var assets embed.FS
var commonPasswords map[string]struct{}
//nolint:gochecknoinits
func init() {
// Password list sourced from:
// https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt
data, err := assets.ReadFile("assets/common-passwords.txt")
if err != nil {
panic(err)
}
passwords := strings.Split(strings.TrimSpace(string(data)), "\n")
commonPasswords = make(map[string]struct{}, len(passwords))
for _, password := range passwords {
commonPasswords[strings.TrimSpace(password)] = struct{}{}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,14 @@ import (
fbErrors "github.com/filebrowser/filebrowser/v2/errors" fbErrors "github.com/filebrowser/filebrowser/v2/errors"
) )
// HashPwd hashes a password. // ValidateAndHashPwd validates and hashes a password.
func HashAndValidatePwd(password string, minimumLength uint) (string, error) { func ValidateAndHashPwd(password string, minimumLength uint) (string, error) {
if uint(len(password)) < minimumLength { if uint(len(password)) < minimumLength {
return "", fbErrors.ErrShortPassword return "", fbErrors.ErrShortPassword{MinimumLength: minimumLength}
}
if _, ok := commonPasswords[password]; ok {
return "", fbErrors.ErrEasyPassword
} }
return HashPwd(password) return HashPwd(password)

6
www/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
docs/LICENSE
docs/code-of-conduct.md
docs/contributing.md
docs/security.md
docs/changelog.md
public

4
www/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM squidfunk/mkdocs-material
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

View File

@ -10,7 +10,7 @@ You are able to customize your File Browser installation by changing its name to
* **Disable external links:** this will disable any external links (except the ones to this documentation). * **Disable external links:** this will disable any external links (except the ones to this documentation).
* **Folder:** is the path to a directory that can contain two items: * **Folder:** is the path to a directory that can contain two items:
* **custom.css**, containing the styles you want to apply to your installation. * **custom.css**, containing the styles you want to apply to your installation.
* **img** a directory whose files can replace the [default logotypes](../frontend/public/img) in the application. * **img** a directory whose files can replace the [default logotypes](https://github.com/filebrowser/filebrowser/tree/master/frontend/public/img) in the application.
These options can be either set via the CLI interface using the following command: These options can be either set via the CLI interface using the following command:

59
www/docs/index.md Normal file
View File

@ -0,0 +1,59 @@
<style>
.md-content .md-typeset h1 {
display: none;
}
</style>
<p align="center">
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
</p>
> [!WARNING]
>
> This project is currently on **maintenance-only** mode. For more information, read the information on [GitHub](https://github.com/filebrowser/filebrowser#project-status).
![Preview](static/example.gif)
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.
## Features
<div class="grid cards" markdown>
- **Easy Login System**
---
![](./static/1.jpg)
- **Sleek Interface**
---
![](./static/2.jpg)
- **User Management**
---
![](./static/3.jpg)
- **File Editing**
---
![](./static/4.jpg)
- **Custom Commands**
---
![](./static/5.jpg)
- **Customization**
---
![](./static/6.jpg)
</div>

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

BIN
www/docs/static/example.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,31 +1,30 @@
site_name: Filebrowser site_name: File Browser
site_description: 'A web-based file browser and manager for your files' site_description: 'A web-based file browser and manager for your files'
site_author: 'Filebrowser Community' site_author: 'File Browser Contributors'
site_url: 'https://filebrowser.org' site_url: 'https://filebrowser.org'
repo_name: 'filebrowser/filebrowser' repo_name: 'filebrowser/filebrowser'
repo_url: 'https://github.com/filebrowser/filebrowser' repo_url: 'https://github.com/filebrowser/filebrowser'
edit_uri: 'edit/master/docs/'
copyright: 'Copyright &copy; 2025 Filebrowser Community' copyright: 'Copyright &copy; 2025 File Browser Contributors'
theme: theme:
name: material name: material
language: en language: en
logo: docs/assets/logo.png logo: static/logo.png
favicon: docs/assets/favicon.png favicon: static/favicon.png
palette: palette:
- scheme: default - scheme: default
primary: blue primary: blue
accent: light blue accent: light blue
toggle: toggle:
icon: material/brightness-7 icon: material/lightbulb
name: Switch to dark mode name: Switch to dark mode
- scheme: slate - scheme: slate
primary: blue primary: blue
accent: light blue accent: light blue
toggle: toggle:
icon: material/brightness-4 icon: material/lightbulb-outline
name: Switch to light mode name: Switch to light mode
font: font:
@ -71,15 +70,7 @@ markdown_extensions:
- md_in_html - md_in_html
- toc: - toc:
permalink: true permalink: true
- pymdownx.arithmatex:
generic: true
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret - pymdownx.caret
- pymdownx.details
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.highlight: - pymdownx.highlight:
anchor_linenums: true anchor_linenums: true
use_pygments: true use_pygments: true
@ -87,11 +78,7 @@ markdown_extensions:
- pymdownx.keys - pymdownx.keys
- pymdownx.mark - pymdownx.mark
- pymdownx.smartsymbols - pymdownx.smartsymbols
- pymdownx.superfences: - pymdownx.superfences
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed: - pymdownx.tabbed:
alternate_style: true alternate_style: true
- pymdownx.tasklist: - pymdownx.tasklist:
@ -110,10 +97,10 @@ extra:
nav: nav:
- Home: index.md - Home: index.md
- Getting Started: - Installation: installation.md
- Installation: docs/installation.md - Configuration: configuration.md
- Configuration: docs/configuration.md
- Security: docs/security.md
- Contributing: - Contributing:
- Contributing: docs/contributing.md - Contributing: contributing.md
- Code of Conduct: docs/code-of-conduct.md - Code of Conduct: code-of-conduct.md
- Security Policy: security.md
- Changelog: changelog.md