mirror of
https://github.com/gtsteffaniak/filebrowser.git
synced 2026-11-08 10:25:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7524b20dca | ||
|
|
2ded6a96c8 | ||
|
|
5cad9f0f0d | ||
|
|
9ed714923f | ||
|
|
6ae2f6feb8 | ||
|
|
e10634078a | ||
|
|
8ee0418dd8 | ||
|
|
b1094e178a | ||
|
|
a2a97bc7ef | ||
|
|
aa39d634e9 | ||
|
|
0fa2cc24b9 | ||
|
|
dba3268237 | ||
|
|
0338f9ede4 | ||
|
|
761451e9c1 | ||
|
|
e5b13e465d | ||
|
|
2e2c8204bb | ||
|
|
505921004b | ||
|
|
259594ebce | ||
|
|
eaeeba3d9e | ||
|
|
db1fdd2e93 | ||
|
|
5992d28e9e | ||
|
|
fb23ea6add | ||
|
|
693f7965f1 | ||
|
|
e6edaf76c8 | ||
|
|
8c2e824da7 | ||
|
|
1ab6e9fb7c | ||
|
|
2d372e1b00 | ||
|
|
dfb181f801 | ||
|
|
29b180ea1c | ||
|
|
92ef5916fe | ||
|
|
dca9097d6c | ||
|
|
1c462b8e96 | ||
|
|
e59c9e327d | ||
|
|
8546fa294f | ||
|
|
0a195b9f95 |
No files matched your search
@@ -0,0 +1,79 @@
|
||||
name: dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev_v*'
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_dev_to_registry:
|
||||
name: Push dev image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
# Workaround to fix error:
|
||||
# failed to push: failed to copy: io: read/write on closed pipe
|
||||
# See https://github.com/docker/build-push-action/issues/761
|
||||
with:
|
||||
driver-opts: |
|
||||
image=moby/buildkit:v0.10.6
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -3,10 +3,48 @@ name: main job
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_latest_to_registry:
|
||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||
name: Push latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -3,11 +3,48 @@ name: pr-merge
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "dev_*"
|
||||
- 'main'
|
||||
- 'dev_v*'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_pr_to_registry:
|
||||
name: Push PR
|
||||
runs-on: ubuntu-latest
|
||||
@@ -41,4 +78,4 @@ jobs:
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -1,44 +0,0 @@
|
||||
name: dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
@@ -3,7 +3,7 @@ name: release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -14,27 +14,63 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
||||
id: extract_branch
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.extract_branch.outputs.branch }}
|
||||
release_name: ${{ steps.extract_branch.outputs.branch }}
|
||||
body: |
|
||||
Please see usage on [readme](https://github.com/gtsteffaniak/filebrowser).
|
||||
Docker repo: https://hub.docker.com/r/gtstef/filebrowser/tags
|
||||
draft: false
|
||||
prerelease: false
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
target_commitish: ${{ steps.extract_branch.outputs.branch }}
|
||||
token: ${{ secrets.PAT }}
|
||||
tag_name: ${{ steps.extract_branch.outputs.branch }}
|
||||
prerelease: false
|
||||
draft: false
|
||||
generate_release_notes: true
|
||||
name: ${{ steps.extract_branch.outputs.branch }}
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_release_to_registry:
|
||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||
name: Push release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
name: tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update_tag:
|
||||
name: Update Release tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- name: Install dependencies and build frontend
|
||||
run: npm i && npm run build
|
||||
working-directory: frontend
|
||||
- name: Install UPX
|
||||
run: sudo apt-get install -y upx
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
args: release --clean
|
||||
workdir: backend
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -10,7 +10,6 @@ rice-box.go
|
||||
/frontend/package-lock.json
|
||||
/backend/vendor
|
||||
/backend/*.cov
|
||||
/backend/test_config.yaml
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||
|
||||
## v0.2.7
|
||||
|
||||
- **Change**: New sidebar style and behavior
|
||||
- **Change**: make search view and button behavior more consistent.
|
||||
- **Fix**: [upload file bug](https://github.com/gtsteffaniak/filebrowser/issues/153)
|
||||
- **Fix**: user lock out bug introduced in 0.2.6
|
||||
- **Fix**: many minor state related issues.
|
||||
|
||||
## v0.2.6
|
||||
|
||||
This change focuses on minimizing and simplifying build process.
|
||||
|
||||
+1
-2
@@ -3,7 +3,7 @@ WORKDIR /app
|
||||
COPY ./frontend/package*.json ./
|
||||
RUN npm i --maxsockets 1
|
||||
COPY ./frontend/ ./
|
||||
RUN npm run build-docker
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:1.22-alpine as base
|
||||
WORKDIR /app
|
||||
@@ -11,7 +11,6 @@ COPY ./backend ./
|
||||
RUN go build -ldflags="-w -s" -o filebrowser .
|
||||
|
||||
FROM alpine:latest
|
||||
ENV FILEBROWSER_NO_EMBEDED="true"
|
||||
ARG app="/app/filebrowser"
|
||||
RUN apk --no-cache add ca-certificates mailcap
|
||||
COPY --from=base /app/filebrowser* ./
|
||||
|
||||
@@ -2,73 +2,70 @@
|
||||
<a href="https://opensource.org/license/apache-2-0/"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License: Apache-2.0"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
|
||||
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
|
||||
</p>
|
||||
<h3 align="center">Filebrowser - A modern web-based file manager</h3>
|
||||
<p align="center">
|
||||
<img width="800" src="https://github.com/user-attachments/assets/8ba93582-aba2-4996-8ac3-25f763a2e596" title="Main Screenshot">
|
||||
<img width="800" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/899152cf-3e69-4179-aa82-752af2df3fc6" title="Main Screenshot">
|
||||
</p>
|
||||
|
||||
> [!WARNING]
|
||||
> Starting with v0.2.0, *ALL* configuration is done via `filebrowser.yaml`
|
||||
> Configuration file.
|
||||
> Starting with v0.2.4 *ALL* share links need to be re-created (due to
|
||||
> security fix).
|
||||
> Starting with v0.2.0, *ALL* configuration is done via `filebrowser.yaml` configuration file.
|
||||
> Starting with v0.2.4 *ALL* share links need to be re-created (due to security fix).
|
||||
|
||||
This fork makes the following significant changes to filebrowser for
|
||||
origin:
|
||||
This fork makes the following significant changes to filebrowser for origin:
|
||||
|
||||
1. [x] Better search
|
||||
- Lightning fast
|
||||
- real-time results as you type
|
||||
- realtime results as you type
|
||||
- Works with more type filters
|
||||
- interactive results page.
|
||||
2. [x] Revamped and simplified GUI navbar and sidebar menu.
|
||||
- Additional compact view mode as well as refreshed view mode
|
||||
styles.
|
||||
- Additional compact view mode as well as refreshed view mode styles.
|
||||
3. [x] Revamped configuration via `filebrowser.yml` config file.
|
||||
- More configurations possible at a per-user level
|
||||
- <img width="450" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/625bd7c4-5ee9-4011-aaae-2a388ab0813b">
|
||||
|
||||
## About
|
||||
|
||||
Filebrowser provides a file managing interface within a specified directory
|
||||
and 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
|
||||
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.
|
||||
|
||||
This repository is a fork, a collection of changes that make this program
|
||||
work better in terms of aesthetics and performance. Improved search,
|
||||
simplified ui (without removing features) and more secure and up-to-date
|
||||
work better in terms of asthetics and performance. Improved search,
|
||||
simplified ui (without removing features) and more secure and up-to-date
|
||||
build are just a few examples.
|
||||
|
||||
This Implementation of filebrowser differs significantly to the original.
|
||||
There are hundreds of thousands of lines changed and they are generally
|
||||
no longer compatible with each other. This has been intentional -- the
|
||||
This Implementation of filebrowser differs significantly to the original.
|
||||
There are hundereds of thousands of lines changed and they are generally
|
||||
no longer compatible with eachother. This has been intentional -- the
|
||||
focus of this fork is on a few key principles:
|
||||
- Simplicity and improved user experience
|
||||
- Efficiency of operations and performance
|
||||
- Minimize external dependencies and standard library usage.
|
||||
- Of course -- adding much-needed features.
|
||||
- Minimizing external dependancies and usage of standard libraries.
|
||||
- Of course -- adding much needed features.
|
||||
|
||||
## Look
|
||||
|
||||
One way you can observe the improved user experience is how I changed
|
||||
the UI. The Navbar is simplified to a three-component system :
|
||||
One way you can observe the improved user experience is how I changed the UI.
|
||||
The Navbar is simplified to a three component system :
|
||||
|
||||
1. (Left) The slide-out action panel button
|
||||
2. (Middle) The powerful search bar.
|
||||
3. (Right) The view change toggle.
|
||||
|
||||
All other functions are moved either into the action menu or popup menus.
|
||||
If the action does not depend on context, it will exist in the slide-out
|
||||
action panel. If the action is available based on context, it will show up as
|
||||
a popup menu.
|
||||
|
||||
All other functions are moved either into the action menu or popup menus.
|
||||
If the action is does not depend on context, it will exist in the slide-out action panel.
|
||||
If the action is available based on context, it will showup as a popup menu.
|
||||
|
||||
<p align="center">
|
||||
<img width="800" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/899152cf-3e69-4179-aa82-752af2df3fc6" title="Main Screenshot">
|
||||
<img width="800" src="https://github.com/user-attachments/assets/18c02d03-5c60-4e15-9c32-3cfe058a0c49" title="Main Screenshot">
|
||||
<img width="800" src="https://github.com/user-attachments/assets/75226dc4-9802-46f0-9e3c-e4403d3275da" title="Main Screenshot">
|
||||
|
||||
<img width="500" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/35cdeb3b-ab79-4b04-8001-8f51f6ea06bb" title="Dark mode">
|
||||
<img width="500" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/55fa4f5c-440e-4a97-b711-96139208a163">
|
||||
<img width="500" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/c76f4100-949b-4e17-a3e6-e410fb8ec08f">
|
||||
<img width="500" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/0bde26f3-fa90-411e-bd0b-abaa47506d62">
|
||||
<img width="560" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/71d8f2b8-6fe6-4fdc-8aac-503d08c28d86">
|
||||
</p>
|
||||
|
||||
## Install
|
||||
@@ -122,45 +119,43 @@ volumes:
|
||||
|
||||
```
|
||||
|
||||
Not using docker (not recommended), download your binary from releases and run with your custom config file:
|
||||
Not using docker (not recommended) (Must donwload asset with frontend directory next to filebrowser binary)
|
||||
|
||||
```
|
||||
./filebrowser -c <filebrowser.yml or other /path/to/config.yaml>
|
||||
./filebrowser -f <filebrowser.yml or other /path/to/config.yaml>
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is now done via a single configuration file:
|
||||
`filebrowser.yaml`, here is an example of minimal [configuration
|
||||
file](./backend/filebrowser.yaml).
|
||||
All configuration is now done via a single configuration file: `filebrowser.yaml`, here is an example minimal [configuration file](./backend/filebrowser.yaml).
|
||||
|
||||
View the [Configuration Help Page](./configuration.md) for available
|
||||
configuration options and other help.
|
||||
View the [Configuration Help Page](./configuration.md) for available configuration options and other help.
|
||||
|
||||
|
||||
## Migration from filebrowser/filebrowser
|
||||
|
||||
If you currently use filebrowser from the filebrowser/filebrowser
|
||||
repo but want to try using this. I recommend you start fresh without
|
||||
reusing the database, but there are a few things you'll need to do if you
|
||||
must migrate:
|
||||
If you are currently using filebrowser from the filebrowser/filebrowser repo, but want to try using this. I recommend you start fresh without reusing the database, but there are a few things you'll need to do if you must migrate:
|
||||
|
||||
1. Create a configuration file as mentioned above.
|
||||
2. Copy your database file from the original filebrowser to the path of
|
||||
the new one.
|
||||
3. Update the configuration file to use the database (under server in
|
||||
filebrowser.yml)
|
||||
4. If you are using docker, update the docker-compose file or docker run
|
||||
command to use the config file as described in the install section
|
||||
above.
|
||||
5. If you are not using docker, just make sure you run filebrowser -c
|
||||
filebrowser.yml and have a valid filebrowser config.
|
||||
2. Copy your database file from the original filebrowser to the path of the new one.
|
||||
3. Update the configuration file to use the database (under server in filebrowser.yml)
|
||||
4. If you are using docker, update the docker-compose file or docker run command to use the config file as described in the install section above.
|
||||
5. If you are not using docker, just make sure you run filebrowser -f filebrowser.yml and have valid filebrowser config.
|
||||
|
||||
|
||||
The filebrowser application should run with the same user and rules that
|
||||
you have from the original. But keep in mind the differences that are
|
||||
mentioned at the top of this readme.
|
||||
The filebrowser application should run with the same user and rules that you have from the original. But keep in mind the differences that are mentioned at the top of this readme.
|
||||
|
||||
### background & help
|
||||
|
||||
The original project filebrowser/filebrowser used multiple different ways to configure the server.
|
||||
This was confusing and difficult to work with from a user and from a developer's perspective.
|
||||
So I completely redesigned the program to use one single human-readable config file.
|
||||
|
||||
I understand many coming from the original fork may notice differences which make using this improved version more difficult. If you notice issues that you believe should be fixed, please open an issue here and it will very likely be addressed with a PR within a few weeks.
|
||||
|
||||
This version of filebrowser is going through a configuration overhaul as mentioned above. Certain features related to rules and commands may not work as they do on the original filebrowser. The purpose of this is to create a more consistent experience where configuration is done via files rather than running commands, so that it's very clear what the current state of the configuration is. When running commands its not clear what the configuration is.
|
||||
|
||||
## Roadmap
|
||||
|
||||
see [Roadmap Page](./roadmap.md)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# .goreleaser.yaml
|
||||
project_name: filebrowser
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
# Build configuration for darwin and linux
|
||||
- id: default
|
||||
ldflags:
|
||||
- -s -w
|
||||
main: main.go
|
||||
binary: filebrowser
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
hooks:
|
||||
post:
|
||||
- upx {{ .Path }} # Compress the binary with UPX
|
||||
|
||||
# Build configuration for windows without arm
|
||||
- id: windows
|
||||
ldflags:
|
||||
- -s -w
|
||||
main: main.go
|
||||
binary: filebrowser
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
||||
format: binary
|
||||
|
||||
checksum:
|
||||
disable: true
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
+8
-32
@@ -13,8 +13,6 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"embed"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@@ -29,11 +27,6 @@ import (
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
var assets embed.FS
|
||||
|
||||
var nonEmbededFS = os.Getenv("FILEBROWSER_NO_EMBEDED") == "true"
|
||||
|
||||
type dirFS struct {
|
||||
http.Dir
|
||||
}
|
||||
@@ -49,7 +42,6 @@ func init() {
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
pflag.Parse()
|
||||
log.Println("Initializing with config file:", *configFlag)
|
||||
log.Println("Embeded Frontend:", !nonEmbededFS)
|
||||
settings.Initialize(*configFlag)
|
||||
}
|
||||
|
||||
@@ -101,31 +93,15 @@ var rootCmd = &cobra.Command{
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||
go cleanupHandler(listener, sigc)
|
||||
if !nonEmbededFS {
|
||||
assetsFs, err := fs.Sub(assets, "dist")
|
||||
if err != nil {
|
||||
log.Fatal("Could not embed frontend. Does backend/cmd/dist exist? Must be built and exist first")
|
||||
}
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
|
||||
checkErr("fbhttp.NewHandler", err)
|
||||
defer listener.Close()
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err)
|
||||
}
|
||||
} else {
|
||||
assetsFs := dirFS{Dir: http.Dir("frontend/dist")}
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
|
||||
checkErr("fbhttp.NewHandler", err)
|
||||
defer listener.Close()
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err)
|
||||
}
|
||||
assetsFs := dirFS{Dir: http.Dir("frontend/dist")}
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
|
||||
checkErr("fbhttp.NewHandler", err)
|
||||
defer listener.Close()
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err)
|
||||
}
|
||||
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
server:
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
root: "./srv"
|
||||
root: "/srv"
|
||||
auth:
|
||||
method: password
|
||||
signup: false
|
||||
|
||||
+23
-23
@@ -1,13 +1,13 @@
|
||||
module github.com/gtsteffaniak/filebrowser
|
||||
|
||||
go 1.22.5
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/asdine/storm/v3 v3.2.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||
github.com/goccy/go-yaml v1.12.0
|
||||
github.com/goccy/go-yaml v1.11.3
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
@@ -15,42 +15,42 @@ require (
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/image v0.18.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/image v0.17.0
|
||||
golang.org/x/text v0.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/fatih/color v1.10.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.11.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.3.4 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
go.etcd.io/bbolt v1.3.10 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
+51
-36
@@ -2,11 +2,12 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
|
||||
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
@@ -31,38 +32,42 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
|
||||
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
|
||||
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -73,11 +78,13 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
|
||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -87,27 +94,30 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
|
||||
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
||||
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
||||
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
@@ -116,22 +126,24 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
|
||||
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -139,21 +151,24 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -162,8 +177,8 @@ golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
|
||||
@@ -46,7 +46,7 @@ var withHashFile = func(fn handleFunc) handleFunc {
|
||||
Fs: fsPath,
|
||||
Path: lastComponent,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
Token: link.Token,
|
||||
|
||||
+19
-13
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
@@ -156,24 +155,30 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
||||
return http.StatusBadRequest, nil
|
||||
}
|
||||
|
||||
if len(req.Which) == 0 || req.Which[0] == "all" {
|
||||
if len(req.Which) == 0 || (len(req.Which) == 1 && req.Which[0] == "all") {
|
||||
if !d.user.Perm.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if req.Data.Password != "" {
|
||||
req.Data.Password, err = users.HashPwd(req.Data.Password)
|
||||
} else {
|
||||
var suser *users.User
|
||||
suser, err = d.store.Users.Get(d.server.Root, d.raw.(uint))
|
||||
req.Data.Password = suser.Password
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
req.Which = []string{}
|
||||
v := reflect.ValueOf(req.Data)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if field.Name != "Password" && field.Name != "Fs" {
|
||||
req.Which = append(req.Which, field.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range req.Which {
|
||||
v = cases.Title(language.English, cases.NoLower).String(v)
|
||||
req.Which[k] = v
|
||||
|
||||
if v == "Password" {
|
||||
if !d.user.Perm.Admin && d.user.LockPassword {
|
||||
return http.StatusForbidden, nil
|
||||
@@ -190,6 +195,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = d.store.Users.Update(req.Data, req.Which...)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
|
||||
@@ -71,7 +71,6 @@ func setDefaults() Settings {
|
||||
},
|
||||
},
|
||||
UserDefaults: UserDefaults{
|
||||
StickySidebar: true,
|
||||
Scope: ".",
|
||||
LockPassword: false,
|
||||
HideDotfiles: true,
|
||||
@@ -93,7 +92,6 @@ func setDefaults() Settings {
|
||||
|
||||
// Apply applies the default options to a user.
|
||||
func (d *UserDefaults) Apply(u *users.User) {
|
||||
u.StickySidebar = d.StickySidebar
|
||||
u.DisableSettings = d.DisableSettings
|
||||
u.DarkMode = d.DarkMode
|
||||
u.Scope = d.Scope
|
||||
|
||||
@@ -68,7 +68,6 @@ type Frontend struct {
|
||||
// UserDefaults is a type that holds the default values
|
||||
// for some fields on User.
|
||||
type UserDefaults struct {
|
||||
StickySidebar bool `json:"stickySidebar"`
|
||||
DarkMode bool `json:"darkMode"`
|
||||
LockPassword bool `json:"lockPassword"`
|
||||
DisableSettings bool `json:"disableSettings,omitempty"`
|
||||
|
||||
@@ -55,6 +55,7 @@ func (st usersBackend) Update(user *users.User, fields ...string) error {
|
||||
if len(fields) == 0 {
|
||||
return st.Save(user)
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
userField := reflect.ValueOf(user).Elem().FieldByName(field)
|
||||
if !userField.IsValid() {
|
||||
@@ -62,9 +63,10 @@ func (st usersBackend) Update(user *users.User, fields ...string) error {
|
||||
}
|
||||
val := userField.Interface()
|
||||
if err := st.db.UpdateField(user, field, val); err != nil {
|
||||
return fmt.Errorf("Error updating user field: %s, error: %v", field, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ type Sorting struct {
|
||||
|
||||
// User describes a user.
|
||||
type User struct {
|
||||
StickySidebar bool `json:"stickySidebar"`
|
||||
DarkMode bool `json:"darkMode"`
|
||||
DisableSettings bool `json:"disableSettings"`
|
||||
ID uint `storm:"id,increment" json:"id"`
|
||||
|
||||
@@ -2,7 +2,7 @@ package version
|
||||
|
||||
var (
|
||||
// Version is the current File Browser version.
|
||||
Version = "(0.2.7)"
|
||||
Version = "(0.2.6)"
|
||||
// CommitSHA is the commmit sha.
|
||||
CommitSHA = "(unknown)"
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
frontend
|
||||
@@ -9,8 +9,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build && cp -R dist/ ../backend/cmd/",
|
||||
"build-docker": "vite build",
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
|
||||
"lint": "npm run typecheck && eslint src/",
|
||||
|
||||
@@ -24,12 +24,6 @@ export async function create(user) {
|
||||
}
|
||||
|
||||
export async function update(user, which = ["all"]) {
|
||||
if (which[0] != "password") {
|
||||
user.password = "";
|
||||
}
|
||||
if (user.username == "publicUser") {
|
||||
return
|
||||
}
|
||||
await fetchURL(`/api/users/${user.id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
<template>
|
||||
<div class="button-group">
|
||||
<button v-if="isDisabled" disabled>
|
||||
No options for folders
|
||||
<button
|
||||
v-for="(btn, index) in buttons"
|
||||
:key="index"
|
||||
:disabled="isDisabled"
|
||||
:class="{ active: activeButton === index && !isDisabled }"
|
||||
@click="setActiveButton(index, btn.label)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</button>
|
||||
<template v-else>
|
||||
<button
|
||||
v-for="(btn, index) in buttons"
|
||||
:key="index"
|
||||
:class="{ active: activeButton === index }"
|
||||
@click="setActiveButton(index, btn.label)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -107,10 +103,6 @@ button:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
button.active {
|
||||
background-color: var(--blue) !important;
|
||||
color: #ffffff;
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
<template>
|
||||
<div v-if="selectedCount > 0" id="file-selection" :class="{ 'dark-mode': isDarkMode }">
|
||||
<span>{{ selectedCount }} selected</span>
|
||||
<div>
|
||||
<action
|
||||
v-if="headerButtons.select"
|
||||
icon="info"
|
||||
:label="$t('buttons.info')"
|
||||
show="info"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.select"
|
||||
icon="check_circle"
|
||||
:label="$t('buttons.selectMultiple')"
|
||||
@action="toggleMultipleSelection"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="selectedCount"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.share"
|
||||
icon="share"
|
||||
:label="$t('buttons.share')"
|
||||
show="share"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.rename"
|
||||
icon="mode_edit"
|
||||
:label="$t('buttons.rename')"
|
||||
show="rename"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.copy"
|
||||
icon="content_copy"
|
||||
:label="$t('buttons.copyFile')"
|
||||
show="copy"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.move"
|
||||
icon="forward"
|
||||
:label="$t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.delete"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
show="delete"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { files as api } from "@/api";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
|
||||
export default {
|
||||
name: "fileSelection",
|
||||
components: {
|
||||
Action,
|
||||
},
|
||||
computed: {
|
||||
isDarkMode() {
|
||||
return getters.isDarkMode();
|
||||
},
|
||||
headerButtons() {
|
||||
return {
|
||||
select: state.selected.length > 0,
|
||||
upload: state.user.perm?.create && state.selected.length > 0,
|
||||
download: state.user.perm.download && state.selected.length > 0,
|
||||
delete: state.selected.length > 0 && state.user.perm.delete,
|
||||
rename: state.selected.length === 1 && state.user.perm.rename,
|
||||
share: state.selected.length === 1 && state.user.perm.share,
|
||||
move: state.selected.length > 0 && state.user.perm.rename,
|
||||
copy: state.selected.length > 0 && state.user.perm?.create,
|
||||
};
|
||||
},
|
||||
selectedCount() {
|
||||
return getters.selectedCount();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleMultipleSelection() {
|
||||
mutations.setMultiple(!state.multiple);
|
||||
mutations.closeHovers();
|
||||
},
|
||||
download() {
|
||||
if (getters.isSingleFileSelected()) {
|
||||
api.download(null, getters.selectedDownloadUrl());
|
||||
return;
|
||||
}
|
||||
mutations.showHover({
|
||||
name: "download",
|
||||
confirm: (format) => {
|
||||
mutations.closeHovers();
|
||||
let files = [];
|
||||
if (state.selected.length > 0) {
|
||||
for (let i of state.selected) {
|
||||
files.push(state.req.items[i].url);
|
||||
}
|
||||
} else {
|
||||
files.push(state.route.path);
|
||||
}
|
||||
try {
|
||||
api.download(format, ...files);
|
||||
showSuccess("download started");
|
||||
} catch (e) {
|
||||
showError("error downloading", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
@media (min-width: 800px) {
|
||||
#file-selection {
|
||||
bottom: 4em;
|
||||
}
|
||||
}
|
||||
|
||||
#file-selection .action {
|
||||
border-radius: 50%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#file-selection > span {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
color: #6f6f6f;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#file-selection .action span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* File Selection */
|
||||
#file-selection {
|
||||
box-shadow: rgba(0, 0, 0, 0.3) 0px 2em 50px 10px;
|
||||
position: fixed;
|
||||
bottom: 4em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
max-width: 30em;
|
||||
z-index: 3;
|
||||
border-radius: 1em;
|
||||
display: flex;
|
||||
width: 90%;
|
||||
}
|
||||
/* File selection */
|
||||
#file-selection.dark-mode {
|
||||
background: var(--surfaceSecondary) !important;
|
||||
}
|
||||
|
||||
#file-selection.dark-mode span {
|
||||
color: var(--textPrimary) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -101,6 +101,39 @@
|
||||
<div class="searchContext">Search Context: {{ getContext }}</div>
|
||||
<div id="result-list">
|
||||
<div>
|
||||
<!-- Loading icon when search is ongoing -->
|
||||
<p v-show="isEmpty && isRunning" id="renew">
|
||||
<i class="material-icons spin">autorenew</i>
|
||||
</p>
|
||||
<!-- Message when no results are found -->
|
||||
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
||||
<p>{{ noneMessage }}</p>
|
||||
<div class="helpButton" @click="toggleHelp()">Help</div>
|
||||
</div>
|
||||
<!-- Help text section -->
|
||||
<div class="helpText" v-if="showHelp">
|
||||
<p>
|
||||
Search occurs on each character you type (3 character minimum for search
|
||||
terms).
|
||||
</p>
|
||||
<p>
|
||||
<b>The index:</b> Search utilizes the index which automatically gets updated
|
||||
on the configured interval (default: 5 minutes). Searching when the program
|
||||
has just started may result in incomplete results.
|
||||
</p>
|
||||
<p>
|
||||
<b>Filter by type:</b> You can have multiple type filters by adding
|
||||
<code>type:condition</code> followed by search terms.
|
||||
</p>
|
||||
<p>
|
||||
<b>Multiple Search terms:</b> Additional terms separated by <code>|</code>,
|
||||
for example <code>"test|not"</code> searches for both terms independently.
|
||||
</p>
|
||||
<p>
|
||||
<b>File size:</b> Searching files by size may have significantly longer
|
||||
search times.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Button groups for filtering search results -->
|
||||
<ButtonGroup
|
||||
@@ -117,7 +150,7 @@
|
||||
:isDisabled="isTypeSelectDisabled"
|
||||
/>
|
||||
<!-- Inputs for filtering by file size -->
|
||||
<div v-if="!foldersOnly" class="sizeConstraints">
|
||||
<div class="sizeConstraints">
|
||||
<div class="sizeInputWrapper">
|
||||
<p>Smaller Than:</p>
|
||||
<input
|
||||
@@ -142,39 +175,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Loading icon when search is ongoing -->
|
||||
<p v-show="isEmpty && isRunning" id="renew">
|
||||
<i class="material-icons spin">autorenew</i>
|
||||
</p>
|
||||
<!-- Message when no results are found -->
|
||||
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
||||
<p>{{ noneMessage }}</p>
|
||||
<div class="helpButton" @click="toggleHelp()">Help</div>
|
||||
</div>
|
||||
<!-- Help text section -->
|
||||
<div class="helpText" v-if="showHelp">
|
||||
<p>
|
||||
Search occurs on each character you type (3 character minimum for search
|
||||
terms).
|
||||
</p>
|
||||
<p>
|
||||
<b>The index:</b> Search utilizes the index which automatically gets updated
|
||||
on the configured interval (default: 5 minutes). Searching when the program
|
||||
has just started may result in incomplete results.
|
||||
</p>
|
||||
<p>
|
||||
<b>Filter by type:</b> You can have multiple type filters by adding
|
||||
<code>type:condition</code> followed by search terms.
|
||||
</p>
|
||||
<p>
|
||||
<b>Multiple Search terms:</b> Additional terms separated by <code>|</code>,
|
||||
for example <code>"test|not"</code> searches for both terms independently.
|
||||
</p>
|
||||
<p>
|
||||
<b>File size:</b> Searching files by size may have significantly longer search
|
||||
times.
|
||||
</p>
|
||||
</div>
|
||||
<!-- List of search results -->
|
||||
<ul v-show="results.length > 0">
|
||||
<li
|
||||
@@ -241,6 +241,7 @@ export default {
|
||||
{ label: "Archives", value: "type:archive" },
|
||||
],
|
||||
value: "",
|
||||
width: window.innerWidth,
|
||||
ongoing: false,
|
||||
results: [],
|
||||
reload: false,
|
||||
@@ -248,15 +249,6 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
largerThan() {
|
||||
this.submit();
|
||||
},
|
||||
smallerThan() {
|
||||
this.submit();
|
||||
},
|
||||
searchTypes() {
|
||||
this.submit();
|
||||
},
|
||||
active(active) {
|
||||
const resultList = document.getElementById("result-list");
|
||||
if (!active) {
|
||||
@@ -294,14 +286,8 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return state.isMobile;
|
||||
},
|
||||
foldersOnly() {
|
||||
return this.isTypeSelectDisabled;
|
||||
},
|
||||
active() {
|
||||
return getters.currentPromptName() == "search";
|
||||
return getters.currentPromptName() === "search";
|
||||
},
|
||||
showOverlay() {
|
||||
return getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
|
||||
@@ -327,6 +313,9 @@ export default {
|
||||
? this.$t("search.typeToSearch")
|
||||
: this.$t("search.pressToSearch");
|
||||
},
|
||||
isMobile() {
|
||||
return this.width <= 800;
|
||||
},
|
||||
isRunning() {
|
||||
return this.ongoing;
|
||||
},
|
||||
@@ -341,7 +330,11 @@ export default {
|
||||
return path;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.width = window.innerWidth;
|
||||
},
|
||||
async navigateTo(url) {
|
||||
mutations.closeHovers();
|
||||
await this.$nextTick();
|
||||
@@ -370,7 +363,6 @@ export default {
|
||||
mutations.showHover("search");
|
||||
},
|
||||
close(event) {
|
||||
this.value = "";
|
||||
event.stopPropagation();
|
||||
mutations.closeHovers();
|
||||
},
|
||||
@@ -398,7 +390,7 @@ export default {
|
||||
return false;
|
||||
}
|
||||
this.searchTypes = this.searchTypes.replace(string + " ", "");
|
||||
if (state.isMobile) {
|
||||
if (this.isMobile) {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
@@ -410,9 +402,7 @@ export default {
|
||||
},
|
||||
async submit(event) {
|
||||
this.showHelp = false;
|
||||
if (event != undefined) {
|
||||
event.preventDefault();
|
||||
}
|
||||
event.preventDefault();
|
||||
if (this.value === "" || this.value.length < 3) {
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
@@ -442,6 +432,12 @@ export default {
|
||||
this.showHelp = !this.showHelp;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -523,7 +519,7 @@ export default {
|
||||
/* Search */
|
||||
#search {
|
||||
background-color: unset !important;
|
||||
z-index: 5;
|
||||
z-index: 3;
|
||||
position: fixed;
|
||||
top: 0.5em;
|
||||
min-width: 35em;
|
||||
@@ -643,10 +639,6 @@ body.rtl #search #result ul > * {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
input.sizeInput:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Search Input Placeholder */
|
||||
#search::-webkit-input-placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
@@ -1,52 +1,18 @@
|
||||
<template>
|
||||
<nav
|
||||
id="sidebar"
|
||||
:class="{ active: active, 'dark-mode': isDarkMode, sticky: user?.stickySidebar }"
|
||||
>
|
||||
<div class="card">
|
||||
<div class="card-wrapper">
|
||||
<button
|
||||
v-if="user.username"
|
||||
@click="navigateTo('/settings/profile')"
|
||||
class="action"
|
||||
>
|
||||
<i class="material-icons">person</i>
|
||||
<span>{{ user.username }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-wrapper" @mouseleave="resetHoverTextToDefault">
|
||||
<span>{{ hoverText }}</span>
|
||||
<div class="quick-toggles">
|
||||
<div
|
||||
:class="{ active: user?.singleClick }"
|
||||
@click="toggleClick"
|
||||
@mouseover="updateHoverText('Toggle single click')"
|
||||
>
|
||||
<i class="material-icons">ads_click</i>
|
||||
</div>
|
||||
<div
|
||||
:class="{ active: user?.darkMode }"
|
||||
@click="toggleDarkMode"
|
||||
@mouseover="updateHoverText('Toggle dark mode')"
|
||||
>
|
||||
<i class="material-icons">dark_mode</i>
|
||||
</div>
|
||||
<div
|
||||
:class="{ active: user?.stickySidebar }"
|
||||
@click="toggleSticky"
|
||||
@mouseover="updateHoverText('Toggle sticky sidebar')"
|
||||
v-if="!isMobile"
|
||||
>
|
||||
<i class="material-icons">push_pin</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav :class="{ active, 'dark-mode': isDarkMode }">
|
||||
<!-- Section for logged-in users -->
|
||||
<div v-if="isLoggedIn" class="sidebar-scroll-list">
|
||||
<template v-if="isLoggedIn">
|
||||
<!-- My Files button -->
|
||||
<button
|
||||
class="action"
|
||||
@click="toRoot"
|
||||
:aria-label="$t('sidebar.myFiles')"
|
||||
:title="$t('sidebar.myFiles')"
|
||||
>
|
||||
<i class="material-icons">folder</i>
|
||||
<span>{{ $t("sidebar.myFiles") }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Buttons visible if user has create permission -->
|
||||
<div v-if="user.perm?.create">
|
||||
<!-- New Folder button -->
|
||||
@@ -70,7 +36,7 @@
|
||||
<span>{{ $t("sidebar.newFile") }}</span>
|
||||
</button>
|
||||
<!-- Upload button -->
|
||||
<button id="upload-button" @click="uploadFunc" class="action">
|
||||
<button id="upload-button" @click="upload($event)" class="action">
|
||||
<i class="material-icons">file_upload</i>
|
||||
<span>Upload file</span>
|
||||
</button>
|
||||
@@ -81,7 +47,7 @@
|
||||
<!-- Settings button -->
|
||||
<button
|
||||
class="action"
|
||||
@click="navigateTo('/settings/global')"
|
||||
@click="toSettings"
|
||||
:aria-label="$t('sidebar.settings')"
|
||||
:title="$t('sidebar.settings')"
|
||||
>
|
||||
@@ -101,30 +67,10 @@
|
||||
<span>{{ $t("sidebar.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isLoggedIn" class="sources card">
|
||||
<span>Sources</span>
|
||||
<div class="inner-card">
|
||||
<!-- My Files button -->
|
||||
<button
|
||||
class="action"
|
||||
@click="navigateTo('/files/')"
|
||||
:aria-label="$t('sidebar.myFiles')"
|
||||
:title="$t('sidebar.myFiles')"
|
||||
>
|
||||
<i class="material-icons">folder</i>
|
||||
<span>{{ $t("sidebar.myFiles") }}</span>
|
||||
<div class="usage-info">
|
||||
<progress-bar :val="usage.usedPercentage" size="medium"></progress-bar>
|
||||
<span style="text-align: center">{{ usage.usedPercentage }}%</span>
|
||||
<span>{{ usage.used }} of {{ usage.total }} used</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Section for non-logged-in users -->
|
||||
<div v-else class="sidebar-scroll-list">
|
||||
<template v-else>
|
||||
<!-- Login button -->
|
||||
<router-link
|
||||
class="action"
|
||||
@@ -146,11 +92,16 @@
|
||||
<i class="material-icons">person_add</i>
|
||||
<span>{{ $t("sidebar.signup") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="buffer"></div>
|
||||
<div class="credits">
|
||||
<span>
|
||||
<!-- Credits and usage information section -->
|
||||
<div class="credits" v-if="isFiles && !disableUsedPercentage && usage">
|
||||
<progress-bar :val="usage.usedPercentage" size="medium"></progress-bar>
|
||||
<span style="text-align: center">{{ usage.usedPercentage }}%</span>
|
||||
<span>{{ usage.used }} of {{ usage.total }} used</span>
|
||||
<br />
|
||||
<span v-if="disableExternal">File Browser</span>
|
||||
<span v-else>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -168,6 +119,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as upload from "@/utils/upload";
|
||||
import * as auth from "@/utils/auth";
|
||||
import {
|
||||
version,
|
||||
@@ -177,7 +129,7 @@ import {
|
||||
noAuth,
|
||||
loginPage,
|
||||
} from "@/utils/constants";
|
||||
import { files, users } from "@/api";
|
||||
import { files as api } from "@/api";
|
||||
import ProgressBar from "@/components/ProgressBar.vue";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
@@ -188,27 +140,14 @@ export default {
|
||||
components: {
|
||||
ProgressBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hoverText: "Quick Toggles", // Initially empty
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (getters.isLoggedIn()) {
|
||||
this.updateUsage();
|
||||
}
|
||||
this.updateUsage();
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return getters.isMobile();
|
||||
},
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
user() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return {};
|
||||
}
|
||||
return state.user;
|
||||
},
|
||||
isDarkMode() {
|
||||
@@ -221,7 +160,7 @@ export default {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
active() {
|
||||
return getters.isSidebarVisible();
|
||||
return getters.currentPromptName() === "sidebar";
|
||||
},
|
||||
signup: () => signup,
|
||||
version: () => version,
|
||||
@@ -229,233 +168,85 @@ export default {
|
||||
disableUsedPercentage: () => disableUsedPercentage,
|
||||
canLogout: () => !noAuth && loginPage,
|
||||
usage: () => state.usage,
|
||||
route: () => state.route,
|
||||
},
|
||||
watch: {
|
||||
route() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
if (!state.user.stickySidebar) {
|
||||
mutations.closeSidebar();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateHoverText(text) {
|
||||
this.hoverText = text;
|
||||
},
|
||||
resetHoverTextToDefault() {
|
||||
this.hoverText = "Quick Toggles"; // Reset to default hover text
|
||||
},
|
||||
toggleClick() {
|
||||
mutations.updateUser({ singleClick: !state.user.singleClick });
|
||||
},
|
||||
toggleDarkMode() {
|
||||
mutations.toggleDarkMode();
|
||||
},
|
||||
toggleSticky() {
|
||||
let newSettings = state.user;
|
||||
newSettings.stickySidebar = !state.user.stickySidebar;
|
||||
users.update(newSettings, ["stickySidebar"]);
|
||||
},
|
||||
async updateUsage() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
console.log("updating usage");
|
||||
|
||||
let path = getters.getRoutePath();
|
||||
let usageStats = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||
if (this.disableUsedPercentage) {
|
||||
return usageStats;
|
||||
}
|
||||
try {
|
||||
let usage = await files.usage(path);
|
||||
let usage = await api.usage(path);
|
||||
usageStats = {
|
||||
used: getHumanReadableFilesize(usage.used / 1024),
|
||||
total: getHumanReadableFilesize(usage.total / 1024),
|
||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||
};
|
||||
} catch (error) {
|
||||
showError("Error fetching usage", error);
|
||||
showError("Error fetching usage:", error);
|
||||
}
|
||||
console.log(usageStats);
|
||||
mutations.setUsage(usageStats);
|
||||
},
|
||||
showHover(value) {
|
||||
return mutations.showHover(value);
|
||||
},
|
||||
navigateTo(path) {
|
||||
this.$router.push({ path: path }, () => {});
|
||||
// Navigate to the root files directory
|
||||
toRoot() {
|
||||
this.$router.push({ path: "/files/" }, () => {});
|
||||
mutations.closeHovers();
|
||||
},
|
||||
// Navigate to the settings page
|
||||
toSettings() {
|
||||
this.$router.push({ path: "/settings" }, () => {});
|
||||
mutations.closeHovers();
|
||||
},
|
||||
// Show the help overlay
|
||||
help() {
|
||||
mutations.showHover("help");
|
||||
},
|
||||
uploadFunc() {
|
||||
mutations.showHover("upload");
|
||||
// Handle file upload
|
||||
upload(event) {
|
||||
return this.$upload(event);
|
||||
},
|
||||
// Handle files selected for upload
|
||||
uploadInput(event) {
|
||||
mutations.closeHovers();
|
||||
|
||||
let files = event.currentTarget.files;
|
||||
let folder_upload =
|
||||
files[0].webkitRelativePath !== undefined && files[0].webkitRelativePath !== "";
|
||||
|
||||
if (folder_upload) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = files[i];
|
||||
files[i].fullPath = file.webkitRelativePath;
|
||||
}
|
||||
}
|
||||
|
||||
let path = getters.getRoutePath();
|
||||
let conflict = upload.checkConflict(files, state.req.items);
|
||||
|
||||
if (conflict) {
|
||||
mutations.showHover({
|
||||
name: "replace",
|
||||
confirm: (event) => {
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
upload.handleFiles(files, path, true);
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
upload.handleFiles(files, path);
|
||||
},
|
||||
// Logout the user
|
||||
logout: auth.logout,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sidebar-scroll-list {
|
||||
overflow: scroll;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
#sidebar {
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1em;
|
||||
padding-top: 5em;
|
||||
width: 20em;
|
||||
position: fixed;
|
||||
z-index: 4;
|
||||
left: -20em;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
transition: 0.5s ease;
|
||||
background-color: #ededed;
|
||||
}
|
||||
|
||||
#sidebar.sticky {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: none) {
|
||||
nav {
|
||||
background-color: transparent;
|
||||
backdrop-filter: blur(16px) invert(0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.usage-info {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
body.rtl nav {
|
||||
left: unset;
|
||||
right: -17em;
|
||||
}
|
||||
|
||||
#sidebar.active {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#sidebar.rtl nav.active {
|
||||
left: unset;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#sidebar > div {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#sidebar .card {
|
||||
overflow: unset !important;
|
||||
}
|
||||
|
||||
#sidebar .action {
|
||||
width: 100%;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
body.rtl .action {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#sidebar .action > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* FOOTER *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
.credits {
|
||||
font-size: 1em;
|
||||
color: var(--textSecondary);
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.credits > span {
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.credits a,
|
||||
.credits a:hover {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.buffer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.quick-toggles {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
margin-top: 0.5em !important;
|
||||
}
|
||||
|
||||
.quick-toggles button {
|
||||
border-radius: 10em;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1em !important;
|
||||
min-height: 4em;
|
||||
box-shadow: 0 2px 2px #00000024, 0 1px 5px #0000001f, 0 3px 1px -2px #0003;
|
||||
/* overflow: auto; */
|
||||
border-radius: 1em;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sources {
|
||||
padding: 1em;
|
||||
margin-top: 0.5em !important;
|
||||
}
|
||||
|
||||
.inner-card {
|
||||
border-radius: 0.5em;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.quick-toggles div {
|
||||
border-radius: 10em;
|
||||
background-color: var(--surfaceSecondary);
|
||||
}
|
||||
|
||||
.quick-toggles div i {
|
||||
font-size: 2em;
|
||||
padding: 0.25em;
|
||||
border-radius: 10em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.action {
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.quick-toggles .active {
|
||||
background-color: var(--blue) !important;
|
||||
border-radius: 10em;
|
||||
}
|
||||
</style>
|
||||
@@ -23,6 +23,4 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
return state.user;
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
return mutations.closeHovers()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
.then(() => {
|
||||
buttons.success("move");
|
||||
this.$router.push({ path: this.dest });
|
||||
mutations.setReload(true);
|
||||
mutations.setReload(true)
|
||||
})
|
||||
.catch((e) => {
|
||||
buttons.done("move");
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
action(overwrite, rename);
|
||||
mutations.setReload(true);
|
||||
mutations.setReload(true)
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -9,134 +9,30 @@
|
||||
</div>
|
||||
|
||||
<div class="card-action full">
|
||||
<div
|
||||
@click="uploadFile"
|
||||
@keypress.enter="uploadFile"
|
||||
class="action"
|
||||
id="focus-prompt"
|
||||
tabindex="1"
|
||||
>
|
||||
<div @click="uploadFile" class="action">
|
||||
<i class="material-icons">insert_drive_file</i>
|
||||
<div class="title">{{ $t("buttons.file") }}</div>
|
||||
</div>
|
||||
<div
|
||||
@click="uploadFolder"
|
||||
@keypress.enter="uploadFolder"
|
||||
class="action"
|
||||
tabindex="2"
|
||||
>
|
||||
<div @click="uploadFolder" class="action">
|
||||
<i class="material-icons">folder</i>
|
||||
<div class="title">{{ $t("buttons.folder") }}</div>
|
||||
</div>
|
||||
<input ref="fileInput" @change="onFilePicked" type="file" style="display: none" />
|
||||
<input
|
||||
ref="folderInput"
|
||||
@change="onFolderPicked"
|
||||
type="file"
|
||||
webkitdirectory
|
||||
directory
|
||||
style="display: none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "vue";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { mutations, state, getters } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "UploadFiles",
|
||||
setup() {
|
||||
const fileInput = ref(null);
|
||||
const folderInput = ref(null);
|
||||
|
||||
const triggerFilePicker = () => {
|
||||
fileInput.value.click();
|
||||
};
|
||||
|
||||
const triggerFolderPicker = () => {
|
||||
folderInput.value.click();
|
||||
};
|
||||
|
||||
const onFilePicked = (event) => {
|
||||
handleFiles(event);
|
||||
};
|
||||
|
||||
const onFolderPicked = (event) => {
|
||||
handleFiles(event);
|
||||
};
|
||||
|
||||
const handleFiles = (event) => {
|
||||
mutations.closeHovers();
|
||||
const files = event.target.files;
|
||||
if (!files) return;
|
||||
|
||||
const folderUpload = !!files[0].webkitRelativePath;
|
||||
const uploadFiles = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const fullPath = folderUpload ? file.webkitRelativePath : undefined;
|
||||
uploadFiles.push({
|
||||
file, // File object directly
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
isDir: false,
|
||||
fullPath,
|
||||
});
|
||||
}
|
||||
|
||||
const path = getters.getRoutePath();
|
||||
const conflict = upload.checkConflict(uploadFiles, state.req.items);
|
||||
|
||||
if (conflict) {
|
||||
mutations.showHover({
|
||||
name: "replace",
|
||||
action: (event) => {
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
upload.handleFiles(uploadFiles, path, false);
|
||||
},
|
||||
confirm: (event) => {
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
upload.handleFiles(uploadFiles, path, true);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
upload.handleFiles(uploadFiles, path, true);
|
||||
mutations.setReload(true);
|
||||
};
|
||||
|
||||
const openUpload = (isFolder) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.webkitdirectory = isFolder;
|
||||
input.addEventListener("change", handleFiles);
|
||||
input.click();
|
||||
};
|
||||
|
||||
const uploadFile = () => {
|
||||
openUpload(false);
|
||||
};
|
||||
|
||||
const uploadFolder = () => {
|
||||
openUpload(true);
|
||||
};
|
||||
|
||||
return {
|
||||
triggerFilePicker,
|
||||
triggerFolderPicker,
|
||||
uploadFile,
|
||||
uploadFolder,
|
||||
onFilePicked,
|
||||
onFolderPicked,
|
||||
};
|
||||
name: "upload",
|
||||
methods: {
|
||||
uploadFile: function () {
|
||||
document.getElementById("upload-input").value = "";
|
||||
document.getElementById("upload-input").click();
|
||||
},
|
||||
uploadFolder: function () {
|
||||
document.getElementById("upload-folder-input").value = "";
|
||||
document.getElementById("upload-folder-input").click();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="filesInUploadCount > 0"
|
||||
class="upload-files"
|
||||
v-bind:class="{ closed: !open }"
|
||||
>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("prompts.uploadFiles", { files: filesInUploadCount }) }}</h2>
|
||||
|
||||
<button
|
||||
class="action"
|
||||
@click="toggle"
|
||||
aria-label="Toggle file upload list"
|
||||
title="Toggle file upload list"
|
||||
>
|
||||
<i class="material-icons">{{
|
||||
open ? "keyboard_arrow_down" : "keyboard_arrow_up"
|
||||
}}</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-content file-icons">
|
||||
<div
|
||||
class="file"
|
||||
v-for="file in filesInUpload"
|
||||
:key="file.id"
|
||||
:data-dir="file.isDir"
|
||||
:data-type="file.type"
|
||||
:aria-label="file.name"
|
||||
>
|
||||
<div class="file-name"><i class="material-icons"></i> {{ file.name }}</div>
|
||||
<div class="file-progress">
|
||||
<div v-bind:style="{ width: file.progress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "uploadFiles",
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filesInUpload() {
|
||||
return getters.filesInUpload(); // Access the getter directly from the store
|
||||
},
|
||||
filesInUploadCount() {
|
||||
return getters.filesInUploadCount(); // Access the getter directly from the store
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<select @change="change" :value="locale">
|
||||
<select v-on:change="change" :value="locale">
|
||||
<option v-for="(value, label) in locales" :key="label" :value="label">
|
||||
{{ $t("languages." + label) }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
@@ -47,8 +47,8 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
change(event) {
|
||||
const target = event.target;
|
||||
change(event: Event) {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
this.$emit("update:locale", target.value);
|
||||
},
|
||||
},
|
||||
|
||||
+88
-11
@@ -1,12 +1,4 @@
|
||||
/* Basic Styles */
|
||||
:root {
|
||||
--background: white;
|
||||
--surfacePrimary: gray;
|
||||
--surfaceSecondary: lightgray;
|
||||
--textPrimary: white;
|
||||
--textSecondary: gray;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Roboto", sans-serif;
|
||||
padding-top: 4em;
|
||||
@@ -74,6 +66,57 @@ over
|
||||
padding-bottom: 4em;
|
||||
}
|
||||
|
||||
/* Navigation Styles */
|
||||
nav {
|
||||
width: 18em;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
padding-top: 4em;
|
||||
left: -19em;
|
||||
z-index: 4;
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
transition: .1s ease left;
|
||||
}
|
||||
|
||||
body.rtl nav {
|
||||
left: unset;
|
||||
right: -17em;
|
||||
}
|
||||
|
||||
nav.active {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
body.rtl nav.active {
|
||||
left: unset;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
nav > div {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
nav .action {
|
||||
width: 100%;
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
font-size: 1.1em;
|
||||
padding: 0.5em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
body.rtl .action {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
nav .action > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
main {
|
||||
@@ -145,6 +188,22 @@ body.rtl .breadcrumbs a {
|
||||
|
||||
|
||||
|
||||
/* File Selection */
|
||||
#file-selection {
|
||||
box-shadow: rgba(0, 0, 0, 0.3) 0px 2em 50px 10px;
|
||||
position: fixed;
|
||||
bottom: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
max-width: 30em;
|
||||
z-index: 1;
|
||||
border-radius: 1em;
|
||||
display: flex;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
height: 3em;
|
||||
@@ -152,13 +211,31 @@ button {
|
||||
border: none;
|
||||
background-color: #f5f5f5;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
/* Add borders */
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
@media (min-width: 800px) {
|
||||
#file-selection {
|
||||
bottom: 4em;
|
||||
}
|
||||
}
|
||||
|
||||
#file-selection .action {
|
||||
border-radius: 50%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#file-selection > span {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
color: #6f6f6f;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#file-selection .action span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#popup-notification {
|
||||
|
||||
@@ -226,6 +226,15 @@
|
||||
background: var(--surfaceSecondary) !important;
|
||||
}
|
||||
|
||||
/* File selection */
|
||||
.dark-mode #file-selection {
|
||||
background: var(--surfaceSecondary) !important;
|
||||
}
|
||||
|
||||
.dark-mode #file-selection span {
|
||||
color: var(--textPrimary) !important;
|
||||
}
|
||||
|
||||
/* Dropdown */
|
||||
.dark-mode #dropdown {
|
||||
background: var(--surfaceSecondary) !important;
|
||||
|
||||
@@ -300,9 +300,11 @@ body.rtl .card .card-title>*:first-child {
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 4em;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 4;
|
||||
z-index: 3;
|
||||
animation: .3s show ease-in;
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ body.rtl #listingView {
|
||||
#listingView.compact .header,
|
||||
#listingView.list .header {
|
||||
border: 1px solid rgba(0, 0, 0, .1);
|
||||
border-color: var(--divider);
|
||||
}
|
||||
|
||||
#listingView.compact .header>div:first-child {
|
||||
@@ -314,9 +315,8 @@ body.rtl #listingView {
|
||||
|
||||
#listingView .header {
|
||||
display: none;
|
||||
background: white;
|
||||
background: var(--surfacePrimary);
|
||||
border-radius: 1em;
|
||||
border: 1px solid rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
#listingView.list .header i {
|
||||
@@ -334,7 +334,9 @@ body.rtl #listingView {
|
||||
padding: .85em;
|
||||
width: 100%;
|
||||
}
|
||||
#listingView.compact .header {
|
||||
|
||||
}
|
||||
#listingView.list .item:first-child {
|
||||
margin-top: .5em;
|
||||
border-top-left-radius: 1em;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@import "./header.css";
|
||||
@import "./listing.css";
|
||||
@import "./listing-icons.css";
|
||||
@import "./upload-files.css";
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
@import './mobile.css';
|
||||
@@ -89,7 +90,7 @@ main .spinner .bounce2 {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.action:not(:disabled):hover {
|
||||
.action:hover {
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
@@ -304,6 +305,44 @@ body.rtl .breadcrumbs .chevron {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* PROMPT *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
.noty_buttons {
|
||||
text-align: right;
|
||||
padding: 0 10px 10px !important;
|
||||
}
|
||||
|
||||
.noty_buttons button {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
box-shadow: 0 0 0 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* FOOTER *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
.credits {
|
||||
font-size: 1em;
|
||||
color: #a5a5a5;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.credits > span {
|
||||
display: block;
|
||||
margin-top: .5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.credits a,
|
||||
.credits a:hover {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* ANIMATIONS *
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
.upload-files .card.floating {
|
||||
left: auto;
|
||||
top: auto;
|
||||
margin: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.upload-files .file {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upload-files .file .file-name {
|
||||
font-size: 1.1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.upload-files .file .file-name i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.upload-files .file .file-progress {
|
||||
margin-top: 2px;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.upload-files .file .file-progress div {
|
||||
height: 100%;
|
||||
background-color: #40c4ff;
|
||||
width: 0;
|
||||
transition: 0.2s ease width;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.upload-files.closed .card-content {
|
||||
display: none;
|
||||
padding: 0em 1em 1em 1em;
|
||||
}
|
||||
|
||||
.upload-files .card .card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8em;
|
||||
padding: 1em 1em 0em;
|
||||
}
|
||||
|
||||
.upload-files.closed .card-title {
|
||||
font-size: 0.7em;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
@media (max-width: 450px) {
|
||||
.upload-files .card.floating {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
import { state } from "./state.js";
|
||||
|
||||
export const getters = {
|
||||
isMobile: () => state.isMobile,
|
||||
isDarkMode: () => {
|
||||
if (state.user == null) {
|
||||
return true;
|
||||
}
|
||||
return state.user.darkMode === true;
|
||||
},
|
||||
isLoggedIn: () => {
|
||||
return state.user !== null && state.user?.username != undefined && state.user?.username != "publicUser";
|
||||
},
|
||||
isLoggedIn: () => state.user !== null,
|
||||
isAdmin: () => state.user.perm?.admin == true,
|
||||
isFiles: () => state.route.name === "Files",
|
||||
isListing: () => getters.isFiles() && state.req.isDir,
|
||||
@@ -20,37 +17,6 @@ export const getters = {
|
||||
let selectedItem = state.selected[0]
|
||||
return state.req.items[selectedItem].url;
|
||||
},
|
||||
isSidebarVisible: () => {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
console.log(getters.currentPromptName());
|
||||
if (typeof getters.currentPromptName() === "string" && !getters.isStickySidebar()) {
|
||||
return false;
|
||||
}
|
||||
console.log(getters.currentView());
|
||||
if (getters.currentView() !== "listingView") {
|
||||
return false;
|
||||
}
|
||||
return state.showSidebar || getters.isStickySidebar();
|
||||
},
|
||||
isStickySidebar: () => {
|
||||
if (getters.isMobile()) {
|
||||
return false
|
||||
}
|
||||
if (!getters.isLoggedIn()) {
|
||||
return true
|
||||
}
|
||||
return state.user?.stickySidebar
|
||||
},
|
||||
showOverlay: () => {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return false
|
||||
}
|
||||
const hasPrompt = getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
|
||||
const shouldOverlaySidebar = getters.isSidebarVisible() && !getters.isStickySidebar()
|
||||
return hasPrompt || shouldOverlaySidebar;
|
||||
},
|
||||
getRoutePath: () => {
|
||||
return state.route.path.endsWith("/")
|
||||
? state.route.path
|
||||
@@ -91,8 +57,10 @@ export const getters = {
|
||||
},
|
||||
|
||||
filesInUploadCount: () => {
|
||||
const uploadsCount = state.upload.length
|
||||
const queueCount = state.queue.length
|
||||
// Ensure state.upload.uploads is an object and state.upload.queue is an array
|
||||
const uploadsCount = typeof state.upload.uploads === 'object' ? Object.keys(state.upload.uploads).length : 0;
|
||||
const queueCount = Array.isArray(state.upload.queue) ? state.upload.queue.length : 0;
|
||||
|
||||
return uploadsCount + queueCount;
|
||||
},
|
||||
|
||||
@@ -109,15 +77,13 @@ export const getters = {
|
||||
|
||||
currentPromptName: () => {
|
||||
// Ensure state.prompts is an array
|
||||
if (!Array.isArray(state.prompts) || state.prompts.length === 0) {
|
||||
if (!Array.isArray(state.prompts)) {
|
||||
return null;
|
||||
}
|
||||
// Check if the name property is a string
|
||||
const lastPrompt = state.prompts[state.prompts.length - 1];
|
||||
if (typeof lastPrompt?.name !== "string") {
|
||||
if (state.prompts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return lastPrompt.name;
|
||||
return state.prompts[state.prompts.length - 1].name;
|
||||
},
|
||||
|
||||
filesInUpload: () => {
|
||||
|
||||
@@ -1,36 +1,8 @@
|
||||
import * as i18n from "@/i18n";
|
||||
import { state } from "./state.js";
|
||||
import { emitStateChanged } from './eventBus'; // Import the function from eventBus.js
|
||||
import { users } from "@/api";
|
||||
|
||||
export const mutations = {
|
||||
setMobile() {
|
||||
state.mobile = window.innerWidth <= 800
|
||||
emitStateChanged();
|
||||
},
|
||||
toggleDarkMode() {
|
||||
mutations.updateUser({ "darkMode": !state.user.darkMode });
|
||||
emitStateChanged();
|
||||
},
|
||||
toggleSidebar() {
|
||||
if (!state.showSidebar && state.user.stickySidebar) {
|
||||
state.user.stickySidebar = false;
|
||||
mutations.updateUser({ "stickySidebar": false }); // turn off sticky when closed
|
||||
return
|
||||
}
|
||||
state.showSidebar = !state.showSidebar;
|
||||
emitStateChanged();
|
||||
},
|
||||
closeSidebar() {
|
||||
if (state.showSidebar) {
|
||||
state.showSidebar = false;
|
||||
emitStateChanged();
|
||||
}
|
||||
},
|
||||
setUpload(value) {
|
||||
state.upload = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
setUsage: (value) => {
|
||||
state.usage = value;
|
||||
emitStateChanged();
|
||||
@@ -81,14 +53,12 @@ export const mutations = {
|
||||
}
|
||||
|
||||
let locale = value.locale;
|
||||
if (locale === "") {
|
||||
value.locale = i18n.detectLocale();
|
||||
if (locale === "") {
|
||||
locale = i18n.detectLocale();
|
||||
}
|
||||
let previousUser = state.user
|
||||
i18n.setLocale(locale);
|
||||
i18n.default.locale = locale;
|
||||
state.user = value;
|
||||
if (state.user != previousUser && state.user.username != "publicUser") {
|
||||
users.update(state.user);
|
||||
}
|
||||
emitStateChanged();
|
||||
},
|
||||
setJWT: (value) => {
|
||||
@@ -123,15 +93,11 @@ export const mutations = {
|
||||
if (state.user === null) {
|
||||
state.user = {};
|
||||
}
|
||||
let previousUser = state.user;
|
||||
state.user = { ...state.user, ...value };
|
||||
if (state.user.locale !== previousUser.locale) {
|
||||
state.user.locale = i18n.detectLocale();
|
||||
i18n.setLocale(state.user.locale);
|
||||
i18n.default.locale = state.user.locale;
|
||||
}
|
||||
if (state.user != previousUser) {
|
||||
users.update(state.user);
|
||||
for (let field in value) {
|
||||
if (field === "locale") {
|
||||
i18n.setLocale(value[field]);
|
||||
}
|
||||
state.user[field] = value[field];
|
||||
}
|
||||
emitStateChanged();
|
||||
},
|
||||
@@ -140,6 +106,7 @@ export const mutations = {
|
||||
state.oldReq = state.req;
|
||||
state.req = value;
|
||||
state.selected = [];
|
||||
|
||||
if (!state.req?.items) return;
|
||||
state.selected = state.req.items
|
||||
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
|
||||
|
||||
@@ -2,8 +2,6 @@ import { reactive } from 'vue';
|
||||
import { detectLocale } from "@/i18n";
|
||||
|
||||
export const state = reactive({
|
||||
isMobile: window.innerWidth <= 800,
|
||||
showSidebar: false,
|
||||
usage: {
|
||||
used: "0 B",
|
||||
total: "0 B",
|
||||
@@ -11,7 +9,6 @@ export const state = reactive({
|
||||
},
|
||||
editor: null,
|
||||
user: {
|
||||
stickySidebar: false,
|
||||
locale: detectLocale(), // Default to the locale from moment
|
||||
viewMode: 'mosaic', // Default to mosaic view
|
||||
hideDotfiles: false, // Default to false, assuming this is a boolean
|
||||
@@ -40,18 +37,18 @@ export const state = reactive({
|
||||
items: [],
|
||||
},
|
||||
jwt: "",
|
||||
progress: 0,
|
||||
loading: false,
|
||||
reload: false,
|
||||
selected: [],
|
||||
multiple: false,
|
||||
upload: {
|
||||
uploads: {},
|
||||
queue: [],
|
||||
progress: [],
|
||||
sizes: [],
|
||||
progress: [], // Array of progress values
|
||||
sizes: [], // Array of sizes
|
||||
},
|
||||
prompts: [],
|
||||
show: null,
|
||||
showShell: false,
|
||||
showConfirm: null,
|
||||
route: {},
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import { state } from "@/store";
|
||||
import url from "@/utils/url";
|
||||
import { files as api } from "@/api";
|
||||
|
||||
export function checkConflict(files, items) {
|
||||
if (typeof items === "undefined" || items === null) {
|
||||
@@ -102,9 +101,10 @@ export function scanFiles(dt) {
|
||||
}
|
||||
|
||||
export function handleFiles(files, base, overwrite = false) {
|
||||
for (const file of files) {
|
||||
const id = state.upload.id;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let id = state.upload.id;
|
||||
let path = base;
|
||||
let file = files[i];
|
||||
|
||||
if (file.fullPath !== undefined) {
|
||||
path += url.encodePath(file.fullPath);
|
||||
@@ -119,19 +119,11 @@ export function handleFiles(files, base, overwrite = false) {
|
||||
const item = {
|
||||
id,
|
||||
path,
|
||||
file: file.file, // Ensure `file.file` is the Blob or File
|
||||
file,
|
||||
overwrite,
|
||||
...(!file.isDir && { type: file.type }),
|
||||
};
|
||||
|
||||
// Upload the file using your API
|
||||
api.post(item.path, item.file, item.overwrite, (event) => {
|
||||
console.log(`Upload progress: ${Math.round((event.loaded / event.total) * 100)}%`);
|
||||
})
|
||||
.then(response => {
|
||||
console.log("Upload successful:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Upload error:", error);
|
||||
});
|
||||
state.dispatch("upload/upload", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { files as api } from "@/api";
|
||||
|
||||
@@ -50,7 +49,7 @@ export default {
|
||||
return getters.currentView() !== null;
|
||||
},
|
||||
reload() {
|
||||
return state.reload;
|
||||
return state.reload; // Access reload from state
|
||||
},
|
||||
},
|
||||
created() {
|
||||
@@ -59,7 +58,8 @@ export default {
|
||||
watch: {
|
||||
$route: "fetchData",
|
||||
reload(value) {
|
||||
if (value) {
|
||||
if (value === true) {
|
||||
console.log("reloading")
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
@@ -78,16 +78,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
// Set loading to true and reset the error.
|
||||
mutations.setLoading(true);
|
||||
this.error = null;
|
||||
|
||||
// Reset view information using mutations
|
||||
mutations.setReload(false);
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
mutations.closeHovers();
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
mutations.setLoading(true);
|
||||
this.error = null;
|
||||
|
||||
let url = state.route.path;
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
@@ -106,10 +106,9 @@ export default {
|
||||
}
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
mutations.setLoading(false);
|
||||
mutations.replaceRequest(data);
|
||||
}
|
||||
mutations.setLoading(false);
|
||||
mutations.replaceRequest(data);
|
||||
},
|
||||
keyEvent(event) {
|
||||
// F1!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template v-if="isLoggedIn">
|
||||
<template>
|
||||
<div>
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
<div v-if="progress" class="progress">
|
||||
@@ -14,17 +14,16 @@
|
||||
></editorBar>
|
||||
<defaultBar :class="{ 'dark-mode-header': isDarkMode }" v-else></defaultBar>
|
||||
<sidebar></sidebar>
|
||||
<search v-if="showSearch"></search>
|
||||
<main :class="{ 'dark-mode': isDarkMode, moveWithSidebar: moveWithSidebar }">
|
||||
<main :class="{ 'dark-mode': isDarkMode }">
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
<prompts :class="{ 'dark-mode': isDarkMode }"></prompts>
|
||||
<upload-files></upload-files>
|
||||
</div>
|
||||
<div class="card" id="popup-notification">
|
||||
<i v-on:click="closePopUp" class="material-icons">close</i>
|
||||
<div id="popup-notification-content">no info</div>
|
||||
</div>
|
||||
<fileSelection> </fileSelection>
|
||||
</template>
|
||||
<script>
|
||||
import editorBar from "./bars/EditorBar.vue";
|
||||
@@ -32,9 +31,7 @@ import defaultBar from "./bars/Default.vue";
|
||||
import listingBar from "./bars/ListingBar.vue";
|
||||
import Prompts from "@/components/prompts/Prompts.vue";
|
||||
import Sidebar from "@/components/Sidebar.vue";
|
||||
import Search from "@/components/Search.vue";
|
||||
import fileSelection from "@/components/FileSelection.vue";
|
||||
|
||||
import UploadFiles from "../components/prompts/UploadFiles.vue";
|
||||
import { closePopUp } from "@/notify";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
@@ -42,13 +39,12 @@ import { state, getters, mutations } from "@/store";
|
||||
export default {
|
||||
name: "layout",
|
||||
components: {
|
||||
fileSelection,
|
||||
Search,
|
||||
defaultBar,
|
||||
editorBar,
|
||||
listingBar,
|
||||
Sidebar,
|
||||
Prompts,
|
||||
UploadFiles,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -58,21 +54,7 @@ export default {
|
||||
itemWeight: 0,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.updateIsMobile);
|
||||
},
|
||||
computed: {
|
||||
showSearch() {
|
||||
return getters.isLoggedIn() && this.currentView == "listingView";
|
||||
},
|
||||
isLoggedIn() {
|
||||
return getters.isLoggedIn();
|
||||
},
|
||||
moveWithSidebar() {
|
||||
return (
|
||||
getters.isSidebarVisible() && !getters.isMobile() && state.user?.stickySidebar
|
||||
);
|
||||
},
|
||||
closePopUp() {
|
||||
return closePopUp;
|
||||
},
|
||||
@@ -95,7 +77,7 @@ export default {
|
||||
return state.user; // Access state directly from the store
|
||||
},
|
||||
showOverlay() {
|
||||
return getters.showOverlay();
|
||||
return getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
|
||||
},
|
||||
isDarkMode() {
|
||||
return getters.isDarkMode();
|
||||
@@ -109,9 +91,6 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
if (getters.currentPromptName() !== "success") {
|
||||
@@ -120,11 +99,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateIsMobile() {
|
||||
mutations.setMobile();
|
||||
},
|
||||
resetPrompts() {
|
||||
mutations.closeSidebar();
|
||||
mutations.closeHovers();
|
||||
},
|
||||
getTitle() {
|
||||
@@ -142,13 +117,7 @@ export default {
|
||||
main {
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
transition: 0.5s ease;
|
||||
}
|
||||
|
||||
main.moveWithSidebar {
|
||||
padding-left: 21em;
|
||||
}
|
||||
|
||||
main::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
||||
@@ -259,10 +259,13 @@ export default {
|
||||
// Set loading to true and reset the error.
|
||||
mutations.setLoading(true);
|
||||
this.error = null;
|
||||
|
||||
// Reset view information.
|
||||
if (!getters.isLoggedIn()) {
|
||||
if (state.user == undefined) {
|
||||
let userData = await api.getPublicUser();
|
||||
mutations.setUser(userData);
|
||||
let req = state.req;
|
||||
req.user = userData;
|
||||
mutations.replaceRequest(req);
|
||||
}
|
||||
mutations.setReload(false);
|
||||
mutations.resetSelected();
|
||||
@@ -273,12 +276,17 @@ export default {
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
|
||||
let file = await api.fetchPub(url, this.password);
|
||||
file.hash = this.hash;
|
||||
this.token = file.token || "";
|
||||
mutations.updateRequest(file);
|
||||
document.title = `${file.name} - ${document.title}`;
|
||||
mutations.setLoading(false);
|
||||
try {
|
||||
let file = await api.fetchPub(url, this.password);
|
||||
file.hash = this.hash;
|
||||
this.token = file.token || "";
|
||||
mutations.updateRequest(file);
|
||||
document.title = `${file.name} - ${document.title}`;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
mutations.setLoading(false);
|
||||
}
|
||||
},
|
||||
keyEvent(event) {
|
||||
// Esc!
|
||||
|
||||
@@ -221,6 +221,13 @@ export default {
|
||||
let uri = url.removeLastDir(state.route.path) + "/";
|
||||
this.$router.push({ path: uri });
|
||||
},
|
||||
toggleSidebar() {
|
||||
if (state.show == "sidebar") {
|
||||
mutations.closeHovers();
|
||||
} else {
|
||||
mutations.showHover("sidebar");
|
||||
}
|
||||
},
|
||||
base64(name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)));
|
||||
},
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
icon="menu"
|
||||
:label="$t('buttons.toggleSidebar')"
|
||||
@action="toggleSidebar()"
|
||||
:disabled="showOverlay"
|
||||
/>
|
||||
<search />
|
||||
<action
|
||||
class="menu-button"
|
||||
icon="grid_view"
|
||||
:label="$t('buttons.switchView')"
|
||||
@action="switchView"
|
||||
:disabled="showOverlay"
|
||||
/>
|
||||
</header>
|
||||
</template>
|
||||
@@ -26,13 +25,16 @@
|
||||
</style>
|
||||
<script>
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import { users, files as api } from "@/api";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import Search from "@/components/Search.vue";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "listingView",
|
||||
components: {
|
||||
Action,
|
||||
Search,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@@ -41,9 +43,6 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showOverlay() {
|
||||
return getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
|
||||
},
|
||||
viewIcon() {
|
||||
const icons = {
|
||||
list: "view_module",
|
||||
@@ -72,15 +71,21 @@ export default {
|
||||
}
|
||||
},
|
||||
toggleSidebar() {
|
||||
mutations.toggleSidebar();
|
||||
if (getters.currentPromptName() === "sidebar") {
|
||||
mutations.closeHovers();
|
||||
} else {
|
||||
mutations.showHover("sidebar");
|
||||
}
|
||||
},
|
||||
async switchView() {
|
||||
mutations.closeHovers();
|
||||
const currentIndex = this.viewModes.indexOf(state.user.viewMode);
|
||||
const nextIndex = (currentIndex + 1) % this.viewModes.length;
|
||||
const newView = this.viewModes[nextIndex];
|
||||
let data = state.user;
|
||||
data.viewMode = this.viewModes[nextIndex];
|
||||
try {
|
||||
mutations.updateUser({ viewMode: newView });
|
||||
users.update(data, ["viewMode"]);
|
||||
mutations.setUser(data);
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,60 @@
|
||||
<template>
|
||||
<div style="padding-bottom: 5em">
|
||||
<div v-if="selectedCount > 0" id="file-selection">
|
||||
<span>{{ selectedCount }} selected</span>
|
||||
<div>
|
||||
<action
|
||||
v-if="headerButtons.select"
|
||||
icon="info"
|
||||
:label="$t('buttons.info')"
|
||||
show="info"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.select"
|
||||
icon="check_circle"
|
||||
:label="$t('buttons.selectMultiple')"
|
||||
@action="toggleMultipleSelection"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="selectedCount"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.share"
|
||||
icon="share"
|
||||
:label="$t('buttons.share')"
|
||||
show="share"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.rename"
|
||||
icon="mode_edit"
|
||||
:label="$t('buttons.rename')"
|
||||
show="rename"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.copy"
|
||||
icon="content_copy"
|
||||
:label="$t('buttons.copyFile')"
|
||||
show="copy"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.move"
|
||||
icon="forward"
|
||||
:label="$t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.delete"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
show="delete"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading">
|
||||
<h2 class="message delayed">
|
||||
<div class="spinner">
|
||||
@@ -39,7 +94,7 @@
|
||||
:class="listingViewMode + ' file-icons'"
|
||||
>
|
||||
<div>
|
||||
<div class="header" :class="{ 'dark-mode-item-header': isDarkMode }" >
|
||||
<div class="header">
|
||||
<p
|
||||
:class="{ active: nameSorted }"
|
||||
class="name"
|
||||
@@ -154,7 +209,13 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style>
|
||||
.header-items {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { files as api } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
@@ -163,10 +224,12 @@ import throttle from "@/utils/throttle";
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import Item from "@/components/files/ListingItem.vue";
|
||||
export default {
|
||||
name: "listingView",
|
||||
components: {
|
||||
Action,
|
||||
Item,
|
||||
},
|
||||
data() {
|
||||
@@ -178,9 +241,6 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isDarkMode() {
|
||||
return state.user?.darkMode;
|
||||
},
|
||||
getMultiple() {
|
||||
return state.multiple;
|
||||
},
|
||||
@@ -261,7 +321,18 @@ export default {
|
||||
listingViewMode() {
|
||||
return state.user?.viewMode;
|
||||
},
|
||||
|
||||
headerButtons() {
|
||||
return {
|
||||
select: state.selected.length > 0,
|
||||
upload: state.user.perm?.create && state.selected.length > 0,
|
||||
download: state.user.perm.download && state.selected.length > 0,
|
||||
delete: state.selected.length > 0 && state.user.perm.delete,
|
||||
rename: state.selected.length === 1 && state.user.perm.rename,
|
||||
share: state.selected.length === 1 && state.user.perm.share,
|
||||
move: state.selected.length > 0 && state.user.perm.rename,
|
||||
copy: state.selected.length > 0 && state.user.perm?.create,
|
||||
};
|
||||
},
|
||||
selectedCount() {
|
||||
return state.selected.length;
|
||||
},
|
||||
@@ -566,12 +637,38 @@ export default {
|
||||
openSearch() {
|
||||
this.currentPrompt = "search";
|
||||
},
|
||||
toggleMultipleSelection() {
|
||||
mutations.setMultiple(!state.multiple);
|
||||
mutations.closeHovers();
|
||||
},
|
||||
windowsResize: throttle(function () {
|
||||
this.colunmsResize();
|
||||
this.width = window.innerWidth;
|
||||
// Listing element is not displayed
|
||||
if (this.$refs.listingView == null) return;
|
||||
}, 100),
|
||||
download() {
|
||||
if (getters.isSingleFileSelected()) {
|
||||
api.download(null, getters.selectedDownloadUrl());
|
||||
return;
|
||||
}
|
||||
mutations.showHover({
|
||||
name: "download",
|
||||
confirm: (format) => {
|
||||
mutations.closeHovers();
|
||||
let files = [];
|
||||
if (state.selected.length > 0) {
|
||||
for (let i of state.selected) {
|
||||
files.push(state.req.items[i].url);
|
||||
}
|
||||
} else {
|
||||
files.push(state.route.path);
|
||||
}
|
||||
|
||||
api.download(format, ...files);
|
||||
},
|
||||
});
|
||||
},
|
||||
upload() {
|
||||
if (
|
||||
typeof window.DataTransferItem !== "undefined" &&
|
||||
@@ -585,15 +682,3 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dark-mode-item-header {
|
||||
border-color: var(--divider) !important;
|
||||
background: var(--surfacePrimary) !important;
|
||||
}
|
||||
.header-items {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -155,9 +155,6 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
this.updatePreview();
|
||||
this.toggleNavigation();
|
||||
},
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<script>
|
||||
import { showSuccess, showError } from "@/notify";
|
||||
import { state, mutations } from "@/store";
|
||||
import { users } from "@/api";
|
||||
import { users as api } from "@/api";
|
||||
import Languages from "@/components/settings/Languages.vue";
|
||||
import ViewMode from "@/components/settings/ViewMode.vue";
|
||||
import i18n, { rtlLanguages } from "@/i18n";
|
||||
@@ -135,16 +135,6 @@ export default {
|
||||
this.singleClick = state.user.singleClick;
|
||||
this.dateFormat = state.user.dateFormat;
|
||||
},
|
||||
watch: {
|
||||
user() {
|
||||
this.darkMode = state.user.darkMode;
|
||||
this.locale = state.user.locale;
|
||||
this.viewMode = state.user.viewMode;
|
||||
this.hideDotfiles = state.user.hideDotfiles;
|
||||
this.singleClick = state.user.singleClick;
|
||||
this.dateFormat = state.user.dateFormat;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async updatePassword(event) {
|
||||
event.preventDefault();
|
||||
@@ -157,7 +147,8 @@ export default {
|
||||
let newUserSettings = state.user;
|
||||
newUserSettings.id = state.user.id;
|
||||
newUserSettings.password = this.password;
|
||||
await users.update(newUserSettings, ["password"]);
|
||||
await api.update(newUserSettings, ["password"]);
|
||||
muations.updateUser(newUserSettings);
|
||||
showSuccess(this.$t("settings.passwordUpdated"));
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
@@ -177,7 +168,7 @@ export default {
|
||||
};
|
||||
const shouldReload =
|
||||
rtlLanguages.includes(data.locale) !== rtlLanguages.includes(i18n.locale);
|
||||
await users.update(data, [
|
||||
await api.update(data, [
|
||||
"locale",
|
||||
"darkMode",
|
||||
"viewMode",
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
setup:
|
||||
cd frontend && npm i
|
||||
if [ ! -f backend/test__config.yaml ]; then \
|
||||
cp backend/filebrowser.yaml backend/test_config.yaml; \
|
||||
fi
|
||||
|
||||
build:
|
||||
docker build -t gtstef/filebrowser .
|
||||
docker build -t gtstef/filebrwoser .
|
||||
|
||||
dev:
|
||||
# Kill processes matching exe/filebrowser, ignore errors if process does not exist
|
||||
-pkill -f "exe/filebrowser" || true
|
||||
# Start backend and frontend concurrently
|
||||
cd backend && FILEBROWSER_NO_EMBEDED=true go run . -c test_config.yaml & BACKEND_PID=$$!; \
|
||||
cd backend && go run . & BACKEND_PID=$$!; \
|
||||
cd frontend && npm run watch & FRONTEND_PID=$$!; \
|
||||
wait $$BACKEND_PID $$FRONTEND_PID
|
||||
|
||||
|
||||
+14
-11
@@ -1,19 +1,22 @@
|
||||
# Planned Roadmap
|
||||
|
||||
Next version :
|
||||
Next version (v0.2.7) :
|
||||
|
||||
- Replace http routes for gorilla/mux
|
||||
- Replace vue-router with simple vanilla js
|
||||
- Theme configuration from settings
|
||||
- Replace afero requests with std library
|
||||
- Add Job status to the sidebar
|
||||
- index status.
|
||||
- new jobs as they come via pocketbase
|
||||
- new jobs as they come
|
||||
|
||||
Future releases:
|
||||
- Replace http routes for gorilla/mux with pocketbase
|
||||
- Allow multiple volumes to show up in the same filebrowser container. https://github.com/filebrowser/filebrowser/issues/2514
|
||||
- enable/disable indexing for certain mounts
|
||||
- Add tools to sidebar
|
||||
- duplicate file detector.
|
||||
- bulk rename https://github.com/filebrowser/filebrowser/issues/2473
|
||||
- job manager - folder sync, copy, lifecycle operations
|
||||
- metrics tracker - user access, file access, download count, last login, etc
|
||||
- support minio s3 and backblaze sources https://github.com/filebrowser/filebrowser/issues/2544
|
||||
|
||||
- Allow multiple volumes to show up in the same filebrowser container. https://github.com/filebrowser/filebrowser/issues/2514
|
||||
- enable/disable indexing for certain mounts
|
||||
- Add tools to sidebar
|
||||
- duplicate file detector.
|
||||
- bulk rename https://github.com/filebrowser/filebrowser/issues/2473
|
||||
- job manager - folder sync, copy, lifecycle operations
|
||||
- metrics tracker - user access, file access, download count, last login, etc
|
||||
- support minio s3 and backblaze sources https://github.com/filebrowser/filebrowser/issues/2544
|
||||
Reference in new issue
Block a user