mirror of
https://github.com/gtsteffaniak/filebrowser.git
synced 2026-11-08 10:25:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e16ed4bda | ||
|
|
21c1899acc | ||
|
|
8f8307b2e0 | ||
|
|
f2bbc43d26 | ||
|
|
d5aafad9fa |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
# These owners will be the default owners for everything in the repo.
|
||||
# Unless a later match takes precedence, @o1egl will be requested for
|
||||
# review when someone opens a pull request.
|
||||
|
||||
* @o1egl
|
||||
@@ -0,0 +1,76 @@
|
||||
name: dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev_v*'
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
||||
- run: cd backend && golangci-lint run
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '21'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && 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@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
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,46 @@ name: main job
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
||||
- run: cd backend && golangci-lint run
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '21'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
|
||||
push_latest_to_registry:
|
||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||
name: Push latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -29,7 +65,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
|
||||
@@ -3,11 +3,46 @@ 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@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
||||
- run: cd backend && golangci-lint run
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '21'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
|
||||
push_pr_to_registry:
|
||||
name: Push PR
|
||||
runs-on: ubuntu-latest
|
||||
@@ -31,7 +66,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
@@ -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,60 @@ 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
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.2
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
target_commitish: ${{ steps.extract_branch.outputs.branch }}
|
||||
token: ${{ secrets.PAT }}
|
||||
tag_name: ${{ steps.extract_branch.outputs.branch }}
|
||||
prerelease: false
|
||||
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
|
||||
generate_release_notes: true
|
||||
name: ${{ steps.extract_branch.outputs.branch }}
|
||||
prerelease: false
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
||||
- run: cd backend && golangci-lint run
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '21'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
push_release_to_registry:
|
||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||
name: Push release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -58,7 +91,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Strip v from version number
|
||||
|
||||
@@ -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 }}
|
||||
@@ -6,11 +6,8 @@ rice-box.go
|
||||
/filebrowser
|
||||
/filebrowser.exe
|
||||
/frontend/dist
|
||||
/frontend/pkg
|
||||
/frontend/package-lock.json
|
||||
/backend/vendor
|
||||
/backend/*.cov
|
||||
/backend/test_config.yaml
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
@@ -2,50 +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.
|
||||
|
||||
- **Change**: Migrated to Vite / Vue 3
|
||||
- **Change**: removed npm modules
|
||||
- replaced vuex with custom state management via src/store
|
||||
- replaced noty with simple card popup notifications
|
||||
- replaced moment with simple date formatter where needed
|
||||
- replaced vue-simple-progress with vue component
|
||||
- **Feature**: improved error logging
|
||||
- backend errors show the root function that called them during the error
|
||||
- frontend errors print errors to console that fail try/catch
|
||||
- all frontend errors via popup notification & print to console as well
|
||||
- **Fix**: Allow editing blank text based files in editor
|
||||
- tweaked listing styles
|
||||
- Feature: Allow disabling the index via configuration yaml
|
||||
|
||||
## v0.2.5
|
||||
|
||||
- Fix: delete user prompt works using native hovers.
|
||||
|
||||
## v0.2.4
|
||||
|
||||
- Feature: [create-folder-feature](https://github.com/gtsteffaniak/filebrowser/pull/105)
|
||||
- Feature: [playable shared video](https://github.com/filebrowser/filebrowser/issues/2537)
|
||||
- Feature: photos, videos, and audio get embedded preview on share instead of icon
|
||||
- Fix: sharable link bug, now uses special publicUser
|
||||
- Bump go version to 1.22
|
||||
- In prep for vue3 migration, npm modules removed:
|
||||
- js-base64
|
||||
- pretty-bytes
|
||||
- whatwg-fetch
|
||||
- lodash.throttle
|
||||
- lodash.clonedeep
|
||||
|
||||
## v0.2.3
|
||||
|
||||
- Feature: token expiration time now configurable
|
||||
|
||||
+10
-7
@@ -1,19 +1,22 @@
|
||||
FROM node:slim as nbuild
|
||||
WORKDIR /app
|
||||
COPY ./frontend/package*.json ./
|
||||
RUN npm i --maxsockets 1
|
||||
COPY ./frontend/package*.json ./
|
||||
RUN npm ci --maxsockets 1
|
||||
COPY ./frontend/ ./
|
||||
RUN npm run build-docker
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:1.22-alpine as base
|
||||
FROM golang:1.21-alpine as base
|
||||
WORKDIR /app
|
||||
COPY ./backend ./
|
||||
COPY ./backend ./
|
||||
RUN go get -u golang.org/x/net
|
||||
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
|
||||
RUN apk --no-cache add \
|
||||
ca-certificates \
|
||||
mailcap
|
||||
WORKDIR /
|
||||
COPY --from=base /app/filebrowser* ./
|
||||
COPY --from=nbuild /app/dist/ ./frontend/dist/
|
||||
ENTRYPOINT [ "./filebrowser" ]
|
||||
@@ -2,75 +2,56 @@
|
||||
<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>
|
||||
<h3 align="center">Filebrowser - A modern file manager for the web</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>
|
||||
|
||||
> [!NOTE]
|
||||
> Only intended to be used with docker.
|
||||
|
||||
> [!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.
|
||||
|
||||
This fork makes the following significant changes to filebrowser for
|
||||
origin:
|
||||
|
||||
1. [x] Better search
|
||||
- Lightning fast
|
||||
- real-time 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.
|
||||
3. [x] Revamped configuration via `filebrowser.yml` config file.
|
||||
- More configurations possible at a per-user level
|
||||
This fork makes the following significant changes to filebrowser for origin:
|
||||
|
||||
1. [x] Better search.
|
||||
- Lightning fast
|
||||
- realtime results as you type
|
||||
- Works with more type filters
|
||||
- interactive results page.
|
||||
1. [x] Revamped and simplified GUI navbar and sidebar menu.
|
||||
1. [x] **IMPORTANT** Revamped configuration via `filebrowser.yml` config file.
|
||||
1. [x] 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">
|
||||
1. [x] Additional compact view mode as well as refreshed view mode styles.
|
||||
|
||||
## 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
|
||||
directory.
|
||||
and it can be used to upload, delete, preview, rename and edit your files.
|
||||
It allows the creation of multiple users and each user can have its own
|
||||
directory. It can be used as a standalone app.
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
## 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 :
|
||||
|
||||
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.
|
||||
|
||||
<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
|
||||
|
||||
Using docker:
|
||||
@@ -78,7 +59,7 @@ Using docker:
|
||||
1. docker run (no persistent db):
|
||||
|
||||
```
|
||||
docker run -it -v /path/to/folder:/srv -p 80:80 gtstef/filebrowser
|
||||
docker run -it -v /path/to/folder:/srv -p 80:8080 gtstef/filebrowser
|
||||
```
|
||||
|
||||
1. docker-compose:
|
||||
@@ -94,7 +75,7 @@ services:
|
||||
- './database:/database' # optional if you want db to persist - configure a path under "database" dir in config file.
|
||||
- './filebrowser.yaml:/filebrowser.yaml' # required
|
||||
ports:
|
||||
- '80:80'
|
||||
- '80:8080'
|
||||
image: gtstef/filebrowser
|
||||
restart: always
|
||||
```
|
||||
@@ -110,7 +91,7 @@ services:
|
||||
- './database:/database' # optional if you want db to persist - configure a path under "database" dir in config file.
|
||||
- './filebrowser.yaml:/filebrowser.yaml' # required
|
||||
ports:
|
||||
- '80:80'
|
||||
- '80:8080'
|
||||
image: gtstef/filebrowser
|
||||
restart: always
|
||||
volumes:
|
||||
@@ -122,45 +103,23 @@ volumes:
|
||||
|
||||
```
|
||||
|
||||
Not using docker (not recommended), download your binary from releases and run with your custom config file:
|
||||
|
||||
```
|
||||
./filebrowser -c <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.
|
||||
|
||||
### background & help
|
||||
|
||||
## Migration from filebrowser/filebrowser
|
||||
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.
|
||||
|
||||
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:
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -145,6 +145,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
log.Println("creds", a.Cred.Password)
|
||||
// create user with the provided credentials
|
||||
d := &users.User{
|
||||
Username: a.Cred.Username,
|
||||
|
||||
@@ -11,34 +11,34 @@ goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/gtsteffaniak/filebrowser/files
|
||||
cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz
|
||||
BenchmarkFillIndex-8 10 3587120 ns/op 273640 B/op 2013 allocs/op
|
||||
BenchmarkSearchAllIndexes-8 10 31291180 ns/op 19500700 B/op 298636 allocs/op
|
||||
BenchmarkFillIndex-8 10 3295862 ns/op 230448 B/op 1927 allocs/op
|
||||
BenchmarkSearchAllIndexes-8 10 30033386 ns/op 19647893 B/op 298702 allocs/op
|
||||
PASS
|
||||
ok github.com/gtsteffaniak/filebrowser/files 0.408s
|
||||
ok github.com/gtsteffaniak/filebrowser/files 0.392s
|
||||
PASS
|
||||
ok github.com/gtsteffaniak/filebrowser/fileutils 0.003s
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 h: 401 <nil>
|
||||
2024/02/07 07:16:43 h: 401 <nil>
|
||||
2024/02/07 07:16:43 h: 401 <nil>
|
||||
2024/02/07 07:16:43 h: 401 <nil>
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 Saving new user: publicUser
|
||||
2024/02/07 07:16:43 h: 401 <nil>
|
||||
2024/02/07 07:16:43 h: 401 <nil>
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 h: 401 <nil>
|
||||
2023/11/24 13:57:20 h: 401 <nil>
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 Saving new user: username
|
||||
2023/11/24 13:57:20 h: 401 <nil>
|
||||
2023/11/24 13:57:20 h: 401 <nil>
|
||||
2023/11/24 13:57:20 h: 401 <nil>
|
||||
2023/11/24 13:57:20 h: 401 <nil>
|
||||
PASS
|
||||
ok github.com/gtsteffaniak/filebrowser/http 0.202s
|
||||
ok github.com/gtsteffaniak/filebrowser/http 0.208s
|
||||
PASS
|
||||
ok github.com/gtsteffaniak/filebrowser/img 0.125s
|
||||
ok github.com/gtsteffaniak/filebrowser/img 0.118s
|
||||
PASS
|
||||
ok github.com/gtsteffaniak/filebrowser/rules 0.002s
|
||||
PASS
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
+22
-47
@@ -3,7 +3,6 @@ package cmd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
@@ -13,8 +12,6 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"embed"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@@ -29,11 +26,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 +41,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)
|
||||
}
|
||||
|
||||
@@ -73,65 +64,49 @@ var rootCmd = &cobra.Command{
|
||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||
}
|
||||
// initialize indexing and schedule indexing ever n minutes (default 5)
|
||||
go files.InitializeIndex(serverConfig.IndexingInterval, serverConfig.Indexing)
|
||||
go files.InitializeIndex(serverConfig.IndexingInterval, true)
|
||||
_, err := os.Stat(serverConfig.Root)
|
||||
checkErr(fmt.Sprint("cmd os.Stat ", serverConfig.Root), err)
|
||||
checkErr(err)
|
||||
var listener net.Listener
|
||||
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
|
||||
switch {
|
||||
case serverConfig.Socket != "":
|
||||
listener, err = net.Listen("unix", serverConfig.Socket)
|
||||
checkErr("net.Listen", err)
|
||||
checkErr(err)
|
||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||
checkErr("cmd.Flags().GetUint32", err)
|
||||
checkErr(err)
|
||||
err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
|
||||
checkErr("os.Chmod", err)
|
||||
checkErr(err)
|
||||
case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
|
||||
cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
|
||||
checkErr("tls.LoadX509KeyPair", err)
|
||||
checkErr(err)
|
||||
listener, err = tls.Listen("tcp", address, &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cer}},
|
||||
)
|
||||
checkErr("tls.Listen", err)
|
||||
checkErr(err)
|
||||
default:
|
||||
listener, err = net.Listen("tcp", address)
|
||||
checkErr("net.Listen", err)
|
||||
checkErr(err)
|
||||
}
|
||||
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(err)
|
||||
defer listener.Close()
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}
|
||||
|
||||
func StartFilebrowser() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal("Error starting filebrowser:", err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,16 +121,16 @@ func quickSetup(d pythonData) {
|
||||
settings.Config.Auth.Key = generateKey()
|
||||
if settings.Config.Auth.Method == "noauth" {
|
||||
err := d.store.Auth.Save(&auth.NoAuth{})
|
||||
checkErr("d.store.Auth.Save", err)
|
||||
checkErr(err)
|
||||
} else {
|
||||
settings.Config.Auth.Method = "password"
|
||||
err := d.store.Auth.Save(&auth.JSONAuth{})
|
||||
checkErr("d.store.Auth.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
err := d.store.Settings.Save(&settings.Config)
|
||||
checkErr("d.store.Settings.Save", err)
|
||||
checkErr(err)
|
||||
err = d.store.Settings.SaveServer(&settings.Config.Server)
|
||||
checkErr("d.store.Settings.SaveServer", err)
|
||||
checkErr(err)
|
||||
user := &users.User{}
|
||||
settings.Config.UserDefaults.Apply(user)
|
||||
user.Username = settings.Config.Auth.AdminUsername
|
||||
@@ -175,5 +150,5 @@ func quickSetup(d pythonData) {
|
||||
Admin: true,
|
||||
}
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
@@ -42,23 +42,23 @@ including 'index_end'.`,
|
||||
},
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
i, err := strconv.Atoi(args[0])
|
||||
checkErr("strconv.Atoi", err)
|
||||
checkErr(err)
|
||||
f := i
|
||||
if len(args) == 2 { //nolint:gomnd
|
||||
f, err = strconv.Atoi(args[1])
|
||||
checkErr("strconv.Atoi", err)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
user := func(u *users.User) {
|
||||
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||
err := d.store.Users.Save(u)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) {
|
||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr("d.store.Settings.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
runRules(d.store, cmd, user, global)
|
||||
|
||||
@@ -33,7 +33,7 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
|
||||
id := getUserIdentifier(cmd.Flags())
|
||||
if id != nil {
|
||||
user, err := st.Users.Get("", id)
|
||||
checkErr("st.Users.Get", err)
|
||||
checkErr(err)
|
||||
|
||||
if usersFn != nil {
|
||||
usersFn(user)
|
||||
@@ -44,7 +44,7 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
|
||||
}
|
||||
|
||||
s, err := st.Settings.Get()
|
||||
checkErr("st.Settings.Get", err)
|
||||
checkErr(err)
|
||||
|
||||
if globalFn != nil {
|
||||
globalFn(s)
|
||||
|
||||
@@ -44,13 +44,13 @@ var rulesAddCmd = &cobra.Command{
|
||||
user := func(u *users.User) {
|
||||
u.Rules = append(u.Rules, rule)
|
||||
err := d.store.Users.Save(u)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) {
|
||||
s.Rules = append(s.Rules, rule)
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr("d.store.Settings.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
runRules(d.store, cmd, user, global)
|
||||
|
||||
@@ -22,19 +22,19 @@ var usersAddCmd = &cobra.Command{
|
||||
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
||||
}
|
||||
servSettings, err := d.store.Settings.GetServer()
|
||||
checkErr("d.store.Settings.GetServer()", err)
|
||||
checkErr(err)
|
||||
// since getUserDefaults() polluted s.Defaults.Scope
|
||||
// which makes the Scope not the one saved in the db
|
||||
// we need the right s.Defaults.Scope here
|
||||
s2, err := d.store.Settings.Get()
|
||||
checkErr("d.store.Settings.Get()", err)
|
||||
checkErr(err)
|
||||
|
||||
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
||||
checkErr("s2.MakeUserDir", err)
|
||||
checkErr(err)
|
||||
user.Scope = userHome
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
checkErr(err)
|
||||
printUsers([]*users.User{user})
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
@@ -16,9 +16,9 @@ path to the file where you want to write the users.`,
|
||||
Args: jsonYamlArg,
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
list, err := d.store.Users.Gets("")
|
||||
checkErr("d.store.Users.Gets", err)
|
||||
checkErr(err)
|
||||
|
||||
err = marshal(args[0], list)
|
||||
checkErr("marshal", err)
|
||||
checkErr(err)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
@@ -46,6 +46,6 @@ var findUsers = python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
list, err = d.store.Users.Gets("")
|
||||
}
|
||||
|
||||
checkErr("findUsers", err)
|
||||
checkErr(err)
|
||||
printUsers(list)
|
||||
}, pythonConfig{})
|
||||
@@ -27,28 +27,28 @@ list or set it to 0.`,
|
||||
Args: jsonYamlArg,
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
fd, err := os.Open(args[0])
|
||||
checkErr("os.Open", err)
|
||||
checkErr(err)
|
||||
defer fd.Close()
|
||||
|
||||
list := []*users.User{}
|
||||
err = unmarshal(args[0], &list)
|
||||
checkErr("unmarshal", err)
|
||||
checkErr(err)
|
||||
|
||||
for _, user := range list {
|
||||
err = user.Clean("")
|
||||
checkErr("Clean", err)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
if mustGetBool(cmd.Flags(), "replace") {
|
||||
oldUsers, err := d.store.Users.Gets("")
|
||||
checkErr("d.store.Users.Gets", err)
|
||||
checkErr(err)
|
||||
|
||||
err = marshal("users.backup.json", list)
|
||||
checkErr("marshal users.backup.json", err)
|
||||
checkErr(err)
|
||||
|
||||
for _, user := range oldUsers {
|
||||
err = d.store.Users.Delete(user.ID)
|
||||
checkErr("d.store.Users.Delete", err)
|
||||
checkErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,7 @@ list or set it to 0.`,
|
||||
// User exists in DB.
|
||||
if err == nil {
|
||||
if !overwrite {
|
||||
newErr := errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered")
|
||||
checkErr("", newErr)
|
||||
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registred"))
|
||||
}
|
||||
|
||||
// If the usernames mismatch, check if there is another one in the DB
|
||||
@@ -69,8 +68,7 @@ list or set it to 0.`,
|
||||
// operation
|
||||
if user.Username != onDB.Username {
|
||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
||||
newErr := usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
||||
checkErr("usernameConflictError", newErr)
|
||||
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -80,7 +78,7 @@ list or set it to 0.`,
|
||||
}
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
checkErr(err)
|
||||
}
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ var usersRmCmd = &cobra.Command{
|
||||
err = d.store.Users.Delete(id)
|
||||
}
|
||||
|
||||
checkErr("usersRmCmd", err)
|
||||
checkErr(err)
|
||||
fmt.Println("user deleted successfully")
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
@@ -29,10 +29,10 @@ options you want to change.`,
|
||||
} else {
|
||||
user, err = d.store.Users.Get("", username)
|
||||
}
|
||||
checkErr("d.store.Users.Get", err)
|
||||
checkErr(err)
|
||||
|
||||
err = d.store.Users.Update(user)
|
||||
checkErr("d.store.Users.Update", err)
|
||||
checkErr(err)
|
||||
printUsers([]*users.User{user})
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
+10
-12
@@ -3,7 +3,6 @@ package cmd
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -18,33 +17,33 @@ import (
|
||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
||||
)
|
||||
|
||||
func checkErr(source string, err error) {
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %v", source, err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||
s, err := flags.GetString(flag)
|
||||
checkErr("mustGetString", err)
|
||||
checkErr(err)
|
||||
return s
|
||||
}
|
||||
|
||||
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
|
||||
b, err := flags.GetBool(flag)
|
||||
checkErr("mustGetBool", err)
|
||||
checkErr(err)
|
||||
return b
|
||||
}
|
||||
|
||||
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
||||
b, err := flags.GetUint(flag)
|
||||
checkErr("mustGetUint", err)
|
||||
checkErr(err)
|
||||
return b
|
||||
}
|
||||
|
||||
func generateKey() []byte {
|
||||
k, err := settings.GenerateKey()
|
||||
checkErr("generateKey", err)
|
||||
checkErr(err)
|
||||
return k
|
||||
}
|
||||
|
||||
@@ -97,19 +96,18 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||
|
||||
data.hadDB = exists
|
||||
db, err := storm.Open(path)
|
||||
checkErr(fmt.Sprintf("storm.Open path %v", path), err)
|
||||
checkErr(err)
|
||||
|
||||
defer db.Close()
|
||||
data.store, err = bolt.NewStorage(db)
|
||||
checkErr("bolt.NewStorage", err)
|
||||
checkErr(err)
|
||||
fn(cmd, args, data)
|
||||
}
|
||||
}
|
||||
|
||||
func marshal(filename string, data interface{}) error {
|
||||
fd, err := os.Create(filename)
|
||||
|
||||
checkErr("os.Create", err)
|
||||
checkErr(err)
|
||||
defer fd.Close()
|
||||
|
||||
switch ext := filepath.Ext(filename); ext {
|
||||
@@ -127,7 +125,7 @@ func marshal(filename string, data interface{}) error {
|
||||
|
||||
func unmarshal(filename string, data interface{}) error {
|
||||
fd, err := os.Open(filename)
|
||||
checkErr("os.Open", err)
|
||||
checkErr(err)
|
||||
defer fd.Close()
|
||||
|
||||
switch ext := filepath.Ext(filename); ext {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
server:
|
||||
port: 80
|
||||
port: 8080
|
||||
baseURL: "/"
|
||||
root: "./srv"
|
||||
root: "/srv"
|
||||
auth:
|
||||
method: password
|
||||
signup: false
|
||||
|
||||
@@ -29,9 +29,10 @@ var textTypes = []string{
|
||||
".yaml",
|
||||
".yml",
|
||||
".json",
|
||||
".bashrc",
|
||||
".zshrc",
|
||||
".env",
|
||||
}
|
||||
|
||||
var compressedFile = []string{
|
||||
".7z",
|
||||
".rar",
|
||||
|
||||
+16
-53
@@ -15,7 +15,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -120,20 +119,15 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
||||
adjustedPath := makeIndexPath(trimmed, index.Root)
|
||||
var info FileInfo
|
||||
info, exists := index.GetMetadataInfo(adjustedPath)
|
||||
if exists && !opts.Content {
|
||||
if exists {
|
||||
// Check if the cache time is less than 1 second
|
||||
if time.Since(info.CacheTime) > time.Second {
|
||||
go RefreshFileInfo(opts)
|
||||
go refreshFileInfo(opts)
|
||||
}
|
||||
// refresh cache after
|
||||
return &info, nil
|
||||
} else {
|
||||
// don't bother caching content
|
||||
if opts.Content {
|
||||
file, err := NewFileInfo(opts)
|
||||
return file, err
|
||||
}
|
||||
updated := RefreshFileInfo(opts)
|
||||
updated := refreshFileInfo(opts)
|
||||
if !updated {
|
||||
file, err := NewFileInfo(opts)
|
||||
return file, err
|
||||
@@ -146,28 +140,27 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func RefreshFileInfo(opts FileOptions) bool {
|
||||
func refreshFileInfo(opts FileOptions) bool {
|
||||
if !opts.Checker.Check(opts.Path) {
|
||||
return false
|
||||
}
|
||||
file, err := stat(opts.Path, opts) // Pass opts.Path here
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
index := GetIndex(rootPath)
|
||||
trimmed := strings.TrimPrefix(opts.Path, "/")
|
||||
if trimmed == "" {
|
||||
trimmed = "/"
|
||||
}
|
||||
adjustedPath := makeIndexPath(trimmed, index.Root)
|
||||
file, err := stat(opts.Path, opts) // Pass opts.Path here
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = file.detectType(adjustedPath, true, opts.Content, opts.ReadHeader)
|
||||
if file.IsDir {
|
||||
err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
//_, exists := index.GetFileMetadata(adjustedPath)
|
||||
|
||||
return index.UpdateFileMetadata(adjustedPath, *file)
|
||||
} else {
|
||||
//_, exists := index.GetFileMetadata(adjustedPath)
|
||||
@@ -198,6 +191,7 @@ func stat(path string, opts FileOptions) (*FileInfo, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if file == nil || file.IsSymlink {
|
||||
info, err := opts.Fs.Stat(opts.Path)
|
||||
if err != nil {
|
||||
@@ -278,37 +272,12 @@ func (i *FileInfo) RealPath() string {
|
||||
return i.Path
|
||||
}
|
||||
|
||||
// addContent reads and sets content based on the file type.
|
||||
func (i *FileInfo) addContent(path string) error {
|
||||
if !i.IsDir {
|
||||
afs := &afero.Afero{Fs: i.Fs}
|
||||
content, err := afs.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stringContent := string(content)
|
||||
if !utf8.ValidString(stringContent) {
|
||||
return nil
|
||||
}
|
||||
if stringContent == "" {
|
||||
i.Content = "empty-file-x6OlSil"
|
||||
return nil
|
||||
}
|
||||
i.Content = stringContent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectType detects the file type.
|
||||
func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool) error {
|
||||
if IsNamedPipe(i.Mode) {
|
||||
i.Type = "blob"
|
||||
if saveContent {
|
||||
return i.addContent(path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var buffer []byte
|
||||
if readHeader {
|
||||
buffer = i.readFirstBytes()
|
||||
@@ -317,20 +286,23 @@ func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool)
|
||||
http.DetectContentType(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
ext := filepath.Ext(i.Name)
|
||||
for _, fileType := range AllFiletypeOptions {
|
||||
if IsMatchingType(ext, fileType) {
|
||||
i.Type = fileType
|
||||
}
|
||||
|
||||
switch i.Type {
|
||||
case "text":
|
||||
if !modify {
|
||||
i.Type = "textImmutable"
|
||||
}
|
||||
if saveContent {
|
||||
return i.addContent(path)
|
||||
afs := &afero.Afero{Fs: i.Fs}
|
||||
content, err := afs.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Content = string(content)
|
||||
}
|
||||
case "video":
|
||||
parentDir := strings.TrimRight(path, i.Name)
|
||||
@@ -338,21 +310,12 @@ func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool)
|
||||
case "doc":
|
||||
if ext == ".pdf" {
|
||||
i.Type = "pdf"
|
||||
return nil
|
||||
}
|
||||
if saveContent {
|
||||
return i.addContent(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i.Type == "" {
|
||||
i.Type = "blob"
|
||||
if saveContent {
|
||||
return i.addContent(path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,24 +28,23 @@ func (si *Index) GetFileMetadata(adjustedPath string) (FileInfo, bool) {
|
||||
|
||||
// UpdateFileMetadata updates the FileInfo for the specified directory in the index.
|
||||
func (si *Index) UpdateFileMetadata(adjustedPath string, info FileInfo) bool {
|
||||
si.mu.Lock()
|
||||
defer si.mu.Unlock()
|
||||
si.mu.RLock()
|
||||
dir, exists := si.Directories[adjustedPath]
|
||||
if !exists {
|
||||
si.mu.RUnlock()
|
||||
if exists {
|
||||
// Initialize the Metadata map if it is nil
|
||||
if dir.Metadata == nil {
|
||||
dir.Metadata = make(map[string]FileInfo)
|
||||
}
|
||||
si.Directories[adjustedPath] = dir
|
||||
// Release the read lock before calling SetFileMetadata
|
||||
}
|
||||
return si.SetFileMetadata(adjustedPath, info)
|
||||
}
|
||||
|
||||
// SetFileMetadata sets the FileInfo for the specified directory in the index.
|
||||
// internal use only
|
||||
func (si *Index) SetFileMetadata(adjustedPath string, info FileInfo) bool {
|
||||
|
||||
si.mu.Lock()
|
||||
defer si.mu.Unlock()
|
||||
_, exists := si.Directories[adjustedPath]
|
||||
if !exists {
|
||||
return false
|
||||
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
+15
-15
@@ -1,27 +1,27 @@
|
||||
module github.com/gtsteffaniak/filebrowser
|
||||
|
||||
go 1.22.5
|
||||
go 1.21
|
||||
|
||||
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.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/marusama/semaphore/v2 v2.5.0
|
||||
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/shirou/gopsutil/v3 v3.23.8
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
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/text v0.16.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/image v0.12.0
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -46,11 +46,11 @@ require (
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // 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
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
+468
-33
@@ -1,3 +1,43 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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=
|
||||
@@ -6,7 +46,16 @@ github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSUL
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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,6 +80,12 @@ 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
@@ -40,6 +95,9 @@ github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
@@ -48,36 +106,96 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
|
||||
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.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54=
|
||||
github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng=
|
||||
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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
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/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
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/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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
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=
|
||||
@@ -85,6 +203,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
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=
|
||||
@@ -97,22 +216,38 @@ github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
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/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
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=
|
||||
@@ -122,59 +257,359 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU
|
||||
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=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
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.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/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-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/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=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
Binary file not shown.
@@ -33,6 +33,7 @@ func (d *data) Check(path string) bool {
|
||||
allow = rule.Allow
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range d.user.Rules {
|
||||
if rule.Matches(path) {
|
||||
allow = rule.Allow
|
||||
|
||||
@@ -69,7 +69,6 @@ func NewHandler(
|
||||
Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET")
|
||||
api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")
|
||||
public := api.PathPrefix("/public").Subrouter()
|
||||
public.Handle("/publicUser", monkey(publicUserGetHandler, "")).Methods("GET")
|
||||
public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET")
|
||||
public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET")
|
||||
return stripPrefix(server.BaseURL, r), nil
|
||||
|
||||
+55
-33
@@ -12,41 +12,35 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
)
|
||||
|
||||
var withHashFile = func(fn handleFunc) handleFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
id, path := ifPathWithName(r)
|
||||
id, ifPath := ifPathWithName(r)
|
||||
link, err := d.store.Share.GetByHash(id)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
if link.Hash != "" {
|
||||
status, err := authenticateShareRequest(r, link)
|
||||
if status != 0 || err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
||||
status, err := authenticateShareRequest(r, link)
|
||||
if status != 0 || err != nil {
|
||||
return status, err
|
||||
}
|
||||
d.user = &users.PublicUser
|
||||
if path == "/" {
|
||||
path = link.Path
|
||||
} else if strings.HasPrefix("/"+path, link.Path) {
|
||||
path = "/" + path
|
||||
} else {
|
||||
path = link.Path + "/" + path
|
||||
|
||||
user, err := d.store.Users.Get(d.server.Root, link.UserID)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
sharePath := settings.Config.Server.Root + path
|
||||
lastComponent := filepath.Base(sharePath)
|
||||
basePath := filepath.Dir(sharePath)
|
||||
fsPath := afero.NewBasePathFs(afero.NewOsFs(), basePath)
|
||||
|
||||
d.user = user
|
||||
|
||||
file, err := files.FileInfoFaster(files.FileOptions{
|
||||
Fs: fsPath,
|
||||
Path: lastComponent,
|
||||
Fs: d.user.Fs,
|
||||
Path: link.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
Token: link.Token,
|
||||
@@ -54,37 +48,64 @@ var withHashFile = func(fn handleFunc) handleFunc {
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
// share base path
|
||||
basePath := link.Path
|
||||
|
||||
// file relative path
|
||||
filePath := ""
|
||||
|
||||
if file.IsDir {
|
||||
basePath = filepath.Dir(basePath)
|
||||
filePath = ifPath
|
||||
}
|
||||
|
||||
// set fs root to the shared file/folder
|
||||
d.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)
|
||||
|
||||
file, err = files.FileInfoFaster(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: filePath,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Checker: d,
|
||||
Token: link.Token,
|
||||
})
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
d.raw = file
|
||||
return fn(w, r, d)
|
||||
}
|
||||
}
|
||||
|
||||
// ref to https://github.com/filebrowser/filebrowser/pull/727
|
||||
// `/api/public/dl/MEEuZK-v/file-name.txt` for old browsers to save file with correct name
|
||||
func ifPathWithName(r *http.Request) (id, filePath string) {
|
||||
pathElements := strings.Split(r.URL.Path, "/")
|
||||
id = pathElements[0]
|
||||
allButFirst := path.Join(pathElements[1:]...)
|
||||
if len(pathElements) == 1 {
|
||||
allButFirst = "/"
|
||||
// prevent maliciously constructed parameters like `/api/public/dl/XZzCDnK2_not_exists_hash_name`
|
||||
// len(pathElements) will be 1, and golang will panic `runtime error: index out of range`
|
||||
|
||||
switch len(pathElements) {
|
||||
case 1:
|
||||
return r.URL.Path, "/"
|
||||
default:
|
||||
return pathElements[0], path.Join("/", path.Join(pathElements[1:]...))
|
||||
}
|
||||
return id, allButFirst
|
||||
}
|
||||
|
||||
var publicShareHandler = withHashFile(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
file := d.raw.(*files.FileInfo)
|
||||
file.Path = strings.TrimPrefix(file.Path, settings.Config.Server.Root)
|
||||
|
||||
if file.IsDir {
|
||||
file.Listing.Sorting = users.Sorting{By: "name", Asc: false}
|
||||
return renderJSON(w, r, file)
|
||||
}
|
||||
|
||||
return renderJSON(w, r, file)
|
||||
})
|
||||
|
||||
var publicUserGetHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
// Call the actual handler logic here (e.g., renderJSON, etc.)
|
||||
// You may need to replace `fn` with the actual handler logic.
|
||||
return renderJSON(w, r, users.PublicUser)
|
||||
}
|
||||
|
||||
var publicDlHandler = withHashFile(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
file := d.raw.(*files.FileInfo)
|
||||
if !file.IsDir {
|
||||
@@ -117,6 +138,7 @@ func authenticateShareRequest(r *http.Request, l *share.Link) (int, error) {
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
expectedStatusCode int
|
||||
}{
|
||||
"Public share, no auth required": {
|
||||
share: &share.Link{Hash: "h"},
|
||||
share: &share.Link{Hash: "h", UserID: 1},
|
||||
req: newHTTPRequest(t),
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
@@ -82,6 +82,9 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
if err := storage.Share.Save(tc.share); err != nil {
|
||||
t.Fatalf("failed to save share: %v", err)
|
||||
}
|
||||
if err := storage.Users.Save(&users.User{Username: "username", Password: "pw"}); err != nil {
|
||||
t.Fatalf("failed to save user: %v", err)
|
||||
}
|
||||
if err := storage.Settings.Save(&settings.Settings{
|
||||
Auth: settings.Auth{
|
||||
Key: []byte("key"),
|
||||
@@ -97,6 +100,7 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := handle(handler, "", storage, &settings.Server{})
|
||||
|
||||
handler.ServeHTTP(recorder, tc.req)
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
@@ -92,6 +92,7 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
if files.IsNamedPipe(file.Mode) {
|
||||
setContentDisposition(w, r, file)
|
||||
return 0, nil
|
||||
@@ -108,6 +109,7 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||
if !d.Check(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := d.user.Fs.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -27,7 +27,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||
Expand: true,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
Content: r.URL.Query().Get("content") == "true",
|
||||
Content: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
@@ -167,6 +167,7 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
||||
w.Header().Set("ETag", etag)
|
||||
return nil
|
||||
}, "save", r.URL.Path, "", d.user)
|
||||
|
||||
return errToStatus(err), err
|
||||
})
|
||||
|
||||
@@ -271,10 +272,6 @@ func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//files.RefreshFileInfo(files.FileOptions{
|
||||
// Fs: info,
|
||||
//})
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
|
||||
+6
-10
@@ -22,6 +22,7 @@ func withPermShare(fn handleFunc) handleFunc {
|
||||
if !d.user.Perm.Share {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
return fn(w, r, d)
|
||||
})
|
||||
}
|
||||
@@ -50,6 +51,7 @@ var shareListHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
return s[i].Expire < s[j].Expire
|
||||
})
|
||||
|
||||
return renderJSON(w, r, s)
|
||||
})
|
||||
|
||||
@@ -75,15 +77,10 @@ var shareDeleteHandler = withPermShare(func(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
err := d.store.Share.Delete(hash)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
return errToStatus(err), err
|
||||
})
|
||||
|
||||
var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
|
||||
var s *share.Link
|
||||
var body share.CreateBody
|
||||
if r.Body != nil {
|
||||
@@ -129,7 +126,7 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
stringHash := ""
|
||||
|
||||
var token string
|
||||
if len(hash) > 0 {
|
||||
tokenBuffer := make([]byte, 24) //nolint:gomnd
|
||||
@@ -137,14 +134,14 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
token = base64.URLEncoding.EncodeToString(tokenBuffer)
|
||||
stringHash = string(hash)
|
||||
}
|
||||
|
||||
s = &share.Link{
|
||||
Path: strings.TrimSuffix(r.URL.Path, "/"),
|
||||
Path: r.URL.Path,
|
||||
Hash: str,
|
||||
Expire: expire,
|
||||
UserID: d.user.ID,
|
||||
PasswordHash: stringHash,
|
||||
PasswordHash: string(hash),
|
||||
Token: token,
|
||||
}
|
||||
|
||||
@@ -156,7 +153,6 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
})
|
||||
|
||||
func getSharePasswordHash(body share.CreateBody) (data []byte, statuscode int, err error) {
|
||||
|
||||
if body.Password == "" {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
auther := raw.(*auth.JSONAuth)
|
||||
|
||||
if auther.ReCaptcha != nil {
|
||||
data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != ""
|
||||
data["ReCaptchaHost"] = auther.ReCaptcha.Host
|
||||
@@ -102,7 +104,7 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
|
||||
}
|
||||
|
||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||
return handleWithStaticData(w, r, d, assetsFs, "public/index.html", "text/html; charset=utf-8")
|
||||
return handleWithStaticData(w, r, d, assetsFs, "index.html", "text/html; charset=utf-8")
|
||||
}, "", store, server)
|
||||
|
||||
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
|
||||
+20
-13
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
@@ -63,6 +62,7 @@ func withSelfOrAdmin(fn handleFunc) handleFunc {
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if d.user.ID != id && !d.user.Perm.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
@@ -156,24 +156,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 +196,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
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
func renderJSON(w http.ResponseWriter, _ *http.Request, data interface{}) (int, error) {
|
||||
marsh, err := json.Marshal(data)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ type Rule struct {
|
||||
Allow bool `json:"allow"`
|
||||
Path string `json:"path"`
|
||||
Regexp *Regexp `json:"regexp"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
// MatchHidden matches paths with a basename
|
||||
|
||||
@@ -101,18 +101,18 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if !blocking {
|
||||
log.Printf("Nonblocking Command: \"%s\"", strings.Join(command, " "))
|
||||
log.Printf("[INFO] Nonblocking Command: \"%s\"", strings.Join(command, " "))
|
||||
defer func() {
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
log.Printf("Nonblocking Command \"%s\" failed: %s", strings.Join(command, " "), err)
|
||||
log.Printf("[INFO] Nonblocking Command \"%s\" failed: %s", strings.Join(command, " "), err)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
log.Printf("Blocking Command: \"%s\"", strings.Join(command, " "))
|
||||
log.Printf("[INFO] Blocking Command: \"%s\"", strings.Join(command, " "))
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -52,13 +52,12 @@ func setDefaults() Settings {
|
||||
ResizePreview: false,
|
||||
EnableExec: false,
|
||||
IndexingInterval: 5,
|
||||
Port: 80,
|
||||
Port: 8080,
|
||||
NumImageProcessors: 4,
|
||||
BaseURL: "",
|
||||
Database: "database.db",
|
||||
Log: "stdout",
|
||||
Root: "/srv",
|
||||
Indexing: true,
|
||||
},
|
||||
Auth: Auth{
|
||||
TokenExpirationTime: "2h",
|
||||
@@ -71,7 +70,6 @@ func setDefaults() Settings {
|
||||
},
|
||||
},
|
||||
UserDefaults: UserDefaults{
|
||||
StickySidebar: true,
|
||||
Scope: ".",
|
||||
LockPassword: false,
|
||||
HideDotfiles: true,
|
||||
@@ -93,7 +91,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
|
||||
|
||||
@@ -54,7 +54,6 @@ type Server struct {
|
||||
Root string `json:"root"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
Indexing bool `json:"indexing"`
|
||||
}
|
||||
|
||||
type Frontend struct {
|
||||
@@ -68,7 +67,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"`
|
||||
|
||||
@@ -7,7 +7,7 @@ server:
|
||||
enableThumbnails: false
|
||||
resizePreview: true
|
||||
typeDetectionByHeader: true
|
||||
port: 80
|
||||
port: 8080
|
||||
baseURL: "/"
|
||||
address: ""
|
||||
log: "stdout"
|
||||
|
||||
@@ -20,7 +20,7 @@ func (s settingsBackend) Save(set *settings.Settings) error {
|
||||
|
||||
func (s settingsBackend) GetServer() (*settings.Server, error) {
|
||||
server := &settings.Server{
|
||||
Port: 80,
|
||||
Port: 8080,
|
||||
NumImageProcessors: 1,
|
||||
}
|
||||
return server, get(s.db, "server", server)
|
||||
|
||||
@@ -45,9 +45,11 @@ func (st usersBackend) Gets() ([]*users.User, error) {
|
||||
if err == storm.ErrNotFound {
|
||||
return nil, errors.ErrNotExist
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return allUsers, err
|
||||
}
|
||||
|
||||
return allUsers, err
|
||||
}
|
||||
|
||||
@@ -55,6 +57,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 +65,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
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/rules"
|
||||
)
|
||||
|
||||
// StorageBackend is the interface to implement for a users storage.
|
||||
@@ -19,7 +18,6 @@ type StorageBackend interface {
|
||||
DeleteByUsername(string) error
|
||||
}
|
||||
|
||||
// Store is an interface for user storage.
|
||||
type Store interface {
|
||||
Get(baseScope string, id interface{}) (user *User, err error)
|
||||
Gets(baseScope string) ([]*User, error)
|
||||
@@ -27,8 +25,6 @@ type Store interface {
|
||||
Save(user *User) error
|
||||
Delete(id interface{}) error
|
||||
LastUpdate(id uint) int64
|
||||
AddRule(username string, rule rules.Rule) error
|
||||
DeleteRule(username string, ruleID string) error
|
||||
}
|
||||
|
||||
// Storage is a users storage.
|
||||
@@ -57,7 +53,7 @@ func (s *Storage) Get(baseScope string, id interface{}) (user *User, err error)
|
||||
if err := user.Clean(baseScope); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, err
|
||||
return
|
||||
}
|
||||
|
||||
// Gets gets a list of all users.
|
||||
@@ -66,6 +62,7 @@ func (s *Storage) Gets(baseScope string) ([]*User, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if err := user.Clean(baseScope); err != nil { //nolint:govet
|
||||
return nil, err
|
||||
@@ -93,48 +90,6 @@ func (s *Storage) Update(user *User, fields ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRule adds a rule to the user's rules list and updates the user in the database.
|
||||
func (s *Storage) AddRule(userID string, rule rules.Rule) error {
|
||||
user, err := s.Get("", userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Rules = append(user.Rules, rule)
|
||||
|
||||
err = s.Update(user, "Rules")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRule deletes a rule specified by ID from the user's rules list and updates the user in the database.
|
||||
func (s *Storage) DeleteRule(userID string, ruleID string) error {
|
||||
user, err := s.Get("", userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find and remove the rule with the specified ID
|
||||
var updatedRules []rules.Rule
|
||||
for _, r := range user.Rules {
|
||||
if r.Id != ruleID {
|
||||
updatedRules = append(updatedRules, r)
|
||||
}
|
||||
}
|
||||
|
||||
user.Rules = updatedRules
|
||||
|
||||
err = s.Update(user, "Rules")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save saves the user in a storage.
|
||||
func (s *Storage) Save(user *User) error {
|
||||
log.Println("Saving new user:", user.Username)
|
||||
|
||||
@@ -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"`
|
||||
@@ -48,24 +47,6 @@ type User struct {
|
||||
DateFormat bool `json:"dateFormat"`
|
||||
}
|
||||
|
||||
var PublicUser = User{
|
||||
Username: "publicUser", // temp user not registered
|
||||
Password: "publicUser", // temp user not registered
|
||||
Scope: "./",
|
||||
ViewMode: "normal",
|
||||
LockPassword: true,
|
||||
Fs: afero.NewMemMapFs(),
|
||||
Perm: Permissions{
|
||||
Create: false,
|
||||
Rename: false,
|
||||
Modify: false,
|
||||
Delete: false,
|
||||
Share: true,
|
||||
Download: true,
|
||||
Admin: false,
|
||||
},
|
||||
}
|
||||
|
||||
// GetRules implements rules.Provider.
|
||||
func (u *User) GetRules() []rules.Rule {
|
||||
return u.Rules
|
||||
|
||||
@@ -2,7 +2,7 @@ package version
|
||||
|
||||
var (
|
||||
// Version is the current File Browser version.
|
||||
Version = "(0.2.7)"
|
||||
Version = "(0.2.3)"
|
||||
// CommitSHA is the commmit sha.
|
||||
CommitSHA = "(unknown)"
|
||||
)
|
||||
+3
-6
@@ -11,7 +11,6 @@ server:
|
||||
CreateUserDir: false
|
||||
UserHomeBasePath: ""
|
||||
indexingInterval: 5
|
||||
indexing: true
|
||||
numImageProcessors: 4
|
||||
socket: ""
|
||||
tlsKey: ""
|
||||
@@ -19,7 +18,7 @@ server:
|
||||
enableThumbnails: false
|
||||
resizePreview: true
|
||||
typeDetectionByHeader: true
|
||||
port: 80
|
||||
port: 8080
|
||||
baseURL: "/"
|
||||
address: ""
|
||||
log: "stdout"
|
||||
@@ -75,7 +74,7 @@ server:
|
||||
enableThumbnails: true
|
||||
enableExec: false
|
||||
indexingInterval: 5
|
||||
port: 80
|
||||
port: 8080
|
||||
numImageProcessors: 4
|
||||
baseURL: ""
|
||||
database: database.db
|
||||
@@ -114,8 +113,6 @@ userDefaults:
|
||||
|
||||
- `indexingInterval`: This is the time in minutes the system waits before checking for filesystem changes. Default: `5`
|
||||
|
||||
- `indexing`: This enables or disables indexing. (Note: search will not work without indexing) Default: `true`
|
||||
|
||||
- `numImageProcessors`: This is the number of image processors available. Default: `4`
|
||||
|
||||
- `socket`: This is the socket configuration.
|
||||
@@ -130,7 +127,7 @@ userDefaults:
|
||||
|
||||
- `typeDetectionByHeader`: This boolean value determines whether type detection is based on headers.
|
||||
|
||||
- `port`: This is the port number on which the server is running. Default: `80`
|
||||
- `port`: This is the port number on which the server is running. Default: `8080`
|
||||
|
||||
- `baseURL`: This is the base URL for the server. Default: `""`
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/eslint-config-typescript"
|
||||
],
|
||||
"rules": {
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-mutating-props": [
|
||||
"error",
|
||||
{
|
||||
"shallowOnly": true
|
||||
}
|
||||
]
|
||||
// no-undef is already included in
|
||||
// @vue/eslint-config-typescript
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
env:
|
||||
browser: true
|
||||
es2021: true
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:vue/vue3-essential
|
||||
parserOptions:
|
||||
ecmaVersion: latest
|
||||
sourceType: module
|
||||
plugins:
|
||||
- vue
|
||||
rules:
|
||||
vue/multi-word-component-names: off
|
||||
vue/no-reserved-component-names: warn
|
||||
vue/no-mutating-props: off
|
||||
vue/no-deprecated-v-bind-sync: warn
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/app"],
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
frontend
|
||||
Generated
+8652
File diff suppressed because it is too large.
Load diff
+36
-30
@@ -1,45 +1,51 @@
|
||||
{
|
||||
"name": "filebrowser-frontend",
|
||||
"version": "3.0.0",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"npm": ">=7.0.0",
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build && cp -R dist/ ../backend/cmd/",
|
||||
"build-docker": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
|
||||
"lint": "npm run typecheck && eslint src/",
|
||||
"lint:fix": "eslint --fix src/",
|
||||
"format": "prettier --write .",
|
||||
"test": "playwright test"
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"fix": "npx vue-cli-service lint",
|
||||
"watch": "vue-cli-service build --watch",
|
||||
"lint": "eslint --ext .vue,.js src/",
|
||||
"lint:fix": "eslint --ext .vue,.js --fix src/",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.24.2",
|
||||
"clipboard": "^2.0.4",
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"js-base64": "^2.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"material-icons": "^1.10.5",
|
||||
"moment": "^2.29.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.10.2",
|
||||
"vue-lazyload": "^3.0.0",
|
||||
"vue-router": "^4.3.0"
|
||||
"noty": "^3.2.0-beta",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"utif": "^3.1.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-async-computed": "^3.9.0",
|
||||
"vue-i18n": "^8.15.3",
|
||||
"vue-lazyload": "^1.3.3",
|
||||
"vue-router": "^3.1.3",
|
||||
"vue-simple-progress": "^1.1.1",
|
||||
"vuex": "^3.1.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.24.0",
|
||||
"vite": "^5.2.7",
|
||||
"vite-plugin-compression2": "^1.0.0",
|
||||
"vue-tsc": "^2.0.7"
|
||||
}
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie < 11"
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
|
||||
[{[ if .ReCaptcha -]}]
|
||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit" data-vite-ignore></script>
|
||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
|
||||
[{[ end ]}]
|
||||
|
||||
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
||||
@@ -121,7 +121,7 @@
|
||||
<div id="app"></div>
|
||||
|
||||
[{[ if .darkMode -]}]
|
||||
<div id="loading" class="dark-mode">
|
||||
<div id="loading dark-mode">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
@@ -136,8 +136,6 @@
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
</div> [{[ end ]}]
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
||||
[{[ if .CSS -]}]
|
||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||
[{[ end ]}]
|
||||
|
||||
+4
-13
@@ -1,24 +1,15 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onMounted } from 'vue';
|
||||
import { mutations } from "@/store"; // Import your store's mutations
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||
|
||||
export default {
|
||||
name: "app",
|
||||
computed: {},
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
mutations.setLoading(false); // Call your mutation or method to set loading to false
|
||||
// Query the loading element and remove it from the DOM
|
||||
const loadingDiv = document.getElementById('loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { state } from "@/store";
|
||||
import store from "@/store";
|
||||
|
||||
const ssl = window.location.protocol === "https:";
|
||||
const protocol = ssl ? "wss:" : "ws:";
|
||||
|
||||
export default function command(url, command, onmessage, onclose) {
|
||||
url = removePrefix(url);
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${state.jwt}`;
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
|
||||
|
||||
let conn = new window.WebSocket(url);
|
||||
conn.onopen = () => conn.send(command);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { state } from "@/store";
|
||||
import store from "@/store";
|
||||
|
||||
export async function fetch(url,content=false) {
|
||||
export async function fetch(url) {
|
||||
url = removePrefix(url);
|
||||
|
||||
const res = await fetchURL(`/api/resources${url}?content=${content}`, {});
|
||||
const res = await fetchURL(`/api/resources${url}`, {});
|
||||
|
||||
let data = await res.json();
|
||||
data.url = `/files${url}`;
|
||||
@@ -70,8 +70,8 @@ export function download(format, ...files) {
|
||||
url += `algo=${format}&`;
|
||||
}
|
||||
|
||||
if (state.jwt) {
|
||||
url += `auth=${state.jwt}&`;
|
||||
if (store.state.jwt) {
|
||||
url += `auth=${store.state.jwt}&`;
|
||||
}
|
||||
|
||||
window.open(url);
|
||||
@@ -95,7 +95,7 @@ export async function post(url, content = "", overwrite = false, onupload) {
|
||||
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
||||
true
|
||||
);
|
||||
request.setRequestHeader("X-Auth", state.jwt);
|
||||
request.setRequestHeader("X-Auth", store.state.jwt);
|
||||
|
||||
if (typeof onupload === "function") {
|
||||
request.upload.onprogress = onupload;
|
||||
|
||||
+14
-32
@@ -1,24 +1,20 @@
|
||||
import { removePrefix, createURL } from "./utils";
|
||||
import { fetchURL, removePrefix, createURL } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
|
||||
export async function fetchPub(url, password = "") {
|
||||
export async function fetch(url, password = "") {
|
||||
url = removePrefix(url);
|
||||
const res = await fetch(
|
||||
`/api/public/share${url}`,
|
||||
{
|
||||
headers: {
|
||||
"X-SHARE-PASSWORD": encodeURIComponent(password),
|
||||
},
|
||||
}
|
||||
|
||||
const res = await fetchURL(
|
||||
`/api/public/share${url}`,
|
||||
{
|
||||
headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) },
|
||||
},
|
||||
false
|
||||
);
|
||||
if (res.status != 200) {
|
||||
const error = new Error("000 No connection");
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
let data = await res.json();
|
||||
data.url = `/share${url}`;
|
||||
|
||||
if (data.isDir) {
|
||||
if (!data.url.endsWith("/")) data.url += "/";
|
||||
data.items = data.items.map((item, index) => {
|
||||
@@ -38,10 +34,12 @@ export async function fetchPub(url, password = "") {
|
||||
|
||||
export function download(format, hash, token, ...files) {
|
||||
let url = `${baseURL}/api/public/dl/${hash}`;
|
||||
|
||||
if (files.length === 1) {
|
||||
url += encodeURIComponent(files[0]) + "?";
|
||||
} else {
|
||||
let arg = "";
|
||||
|
||||
for (let file of files) {
|
||||
arg += encodeURIComponent(file) + ",";
|
||||
}
|
||||
@@ -62,27 +60,11 @@ export function download(format, hash, token, ...files) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
export function getPublicUser() {
|
||||
return fetch("/api/public/publicUser")
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching public user:", error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
export function getDownloadURL(share, inline = false) {
|
||||
const params = {
|
||||
...(inline && { inline: "true" }),
|
||||
...(share.token && { token: share.token }),
|
||||
};
|
||||
if (share.path == undefined) {
|
||||
share.path = ""
|
||||
}
|
||||
return createURL("api/public/dl/" + share.hash + "/"+share.path, params, false);
|
||||
|
||||
return createURL("api/public/dl/" + share.hash + share.path, params, false);
|
||||
}
|
||||
@@ -18,7 +18,6 @@ export async function remove(hash) {
|
||||
export async function create(url, password = "", expires = "", unit = "hours") {
|
||||
url = removePrefix(url);
|
||||
url = `/api/share${url}`;
|
||||
expires = String(expires);
|
||||
if (expires !== "") {
|
||||
url += `?expires=${expires}&unit=${unit}`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { fetchURL, fetchJSON } from "@/api/utils";
|
||||
import { fetchURL, fetchJSON } from "./utils";
|
||||
|
||||
export async function getAllUsers() {
|
||||
return await fetchJSON(`/api/users`, {});
|
||||
export async function getAll() {
|
||||
return fetchJSON(`/api/users`, {});
|
||||
}
|
||||
|
||||
export async function get(id) {
|
||||
@@ -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({
|
||||
|
||||
+11
-11
@@ -1,8 +1,7 @@
|
||||
import { state } from "@/store";
|
||||
import store from "@/store";
|
||||
import { renew, logout } from "@/utils/auth";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { encodePath } from "@/utils/url";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export async function fetchURL(url, opts, auth = true) {
|
||||
opts = opts || {};
|
||||
@@ -14,22 +13,22 @@ export async function fetchURL(url, opts, auth = true) {
|
||||
try {
|
||||
res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
"X-Auth": state.jwt,
|
||||
"sessionId": state.sessionId,
|
||||
"userScope": state.user.scope,
|
||||
"X-Auth": store.state.jwt,
|
||||
"sessionId": store.state.sessionId,
|
||||
"userScope": store.state.user.scope,
|
||||
...headers,
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch {
|
||||
const error = new Error("000 No connection");
|
||||
error.status = res.status;
|
||||
error.status = 0;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (auth && res.headers.get("X-Renew-Token") === "true") {
|
||||
await renew(state.jwt);
|
||||
await renew(store.state.jwt);
|
||||
}
|
||||
|
||||
if (res.status < 200 || res.status > 299) {
|
||||
@@ -48,16 +47,17 @@ export async function fetchURL(url, opts, auth = true) {
|
||||
|
||||
export async function fetchJSON(url, opts) {
|
||||
const res = await fetchURL(url, opts);
|
||||
|
||||
if (res.status === 200) {
|
||||
return res.json();
|
||||
} else {
|
||||
showError("unable to fetch : " + url + "status" + res.status);
|
||||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export function removePrefix(url) {
|
||||
url = url.split("/").splice(2).join("/");
|
||||
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
return url;
|
||||
@@ -71,7 +71,7 @@ export function createURL(endpoint, params = {}, auth = true) {
|
||||
const url = new URL(prefix + encodePath(endpoint), origin);
|
||||
|
||||
const searchParams = {
|
||||
...(auth && { auth: state.jwt }),
|
||||
...(auth && { auth: store.state.jwt }),
|
||||
...params,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,26 +10,21 @@
|
||||
</component>
|
||||
|
||||
<span v-for="(link, index) in items" :key="index">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<span class="chevron"
|
||||
><i class="material-icons">keyboard_arrow_right</i></span
|
||||
>
|
||||
<component :is="element" :to="link.url">{{ link.name }}</component>
|
||||
</span>
|
||||
<action style="display: contents" v-if="showShare" icon="share" show="share" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { state, mutations } from "@/store"; // Import mutations as well
|
||||
import Action from "@/components/header/Action.vue";
|
||||
|
||||
export default {
|
||||
name: "breadcrumbs",
|
||||
components: {
|
||||
Action,
|
||||
},
|
||||
props: ["base", "noLink"],
|
||||
computed: {
|
||||
items() {
|
||||
const relativePath = state.route.path.replace(this.base, "");
|
||||
const relativePath = this.$route.path.replace(this.base, "");
|
||||
let parts = relativePath.split("/");
|
||||
|
||||
if (parts[0] === "") {
|
||||
@@ -73,19 +68,8 @@ export default {
|
||||
|
||||
return "router-link";
|
||||
},
|
||||
showShare() {
|
||||
// Ensure user properties are accessed safely
|
||||
if (state.route.path.startsWith("/share")) {
|
||||
return false;
|
||||
}
|
||||
return state.user?.perm && state.user?.perm.share; // Access from state directly
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// Example of a method using mutations
|
||||
updateUserPermissions(newPerms) {
|
||||
mutations.updateUser({ perm: newPerms })
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,18 +1,9 @@
|
||||
<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>
|
||||
|
||||
@@ -25,7 +16,7 @@ export default {
|
||||
},
|
||||
isDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: false
|
||||
},
|
||||
initialActive: {
|
||||
type: Number,
|
||||
@@ -40,12 +31,15 @@ export default {
|
||||
methods: {
|
||||
setActiveButton(index, label) {
|
||||
if (label == "Only Folders" && this.activeButton != index) {
|
||||
console.log("Only Folders && this.activeButton != index")
|
||||
this.$emit("disableAll");
|
||||
}
|
||||
if (label == "Only Folders" && this.activeButton == index) {
|
||||
console.log("Only Folders && this.activeButton == index")
|
||||
this.$emit("enableAll");
|
||||
}
|
||||
if (label == "Only Files" && this.activeButton != index) {
|
||||
console.log("Only Files && this.activeButton != index")
|
||||
this.$emit("enableAll");
|
||||
}
|
||||
// If the clicked button is already active, de-select it
|
||||
@@ -78,7 +72,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.button-group {
|
||||
margin: 1em;
|
||||
margin:1em;
|
||||
display: flex;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 1em;
|
||||
@@ -99,7 +93,7 @@ button {
|
||||
}
|
||||
|
||||
/* Remove the border from the last button */
|
||||
.button-group > button:last-child {
|
||||
.button-group>button:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
@@ -107,12 +101,8 @@ button:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
button.active {
|
||||
background-color: var(--blue) !important;
|
||||
background-color: var(--blue);
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
@@ -1,221 +0,0 @@
|
||||
<!-- This component taken directly from vue-simple-progress
|
||||
since it didnt support Vue 3 but the component itself does
|
||||
https://raw.githubusercontent.com/dzwillia/vue-simple-progress/master/src/components/Progress.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="vue-simple-progress-text"
|
||||
:style="text_style"
|
||||
v-if="text.length > 0 && textPosition == 'top'"
|
||||
>
|
||||
{{ text }}
|
||||
</div>
|
||||
<div class="vue-simple-progress" :style="progress_style">
|
||||
<div
|
||||
class="vue-simple-progress-text"
|
||||
:style="text_style"
|
||||
v-if="text.length > 0 && textPosition == 'middle'"
|
||||
>
|
||||
{{ text }}
|
||||
</div>
|
||||
<div
|
||||
style="position: relative; left: -9999px"
|
||||
:style="text_style"
|
||||
v-if="text.length > 0 && textPosition == 'inside'"
|
||||
>
|
||||
{{ text }}
|
||||
</div>
|
||||
<div class="vue-simple-progress-bar" :style="bar_style">
|
||||
<div :style="text_style" v-if="text.length > 0 && textPosition == 'inside'">
|
||||
{{ text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="vue-simple-progress-text"
|
||||
:style="text_style"
|
||||
v-if="text.length > 0 && textPosition == 'bottom'"
|
||||
>
|
||||
{{ text }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// We're leaving this untouched as you can read in the beginning
|
||||
var isNumber = function (n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "progress-bar",
|
||||
props: {
|
||||
val: {
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
default: 100,
|
||||
},
|
||||
size: {
|
||||
// either a number (pixel width/height) or 'tiny', 'small',
|
||||
// 'medium', 'large', 'huge', 'massive' for common sizes
|
||||
default: 3,
|
||||
},
|
||||
"bg-color": {
|
||||
type: String,
|
||||
default: "#eee",
|
||||
},
|
||||
"bar-color": {
|
||||
type: String,
|
||||
default: "#2196f3", // match .blue color to Material Design's 'Blue 500' color
|
||||
},
|
||||
"bar-transition": {
|
||||
type: String,
|
||||
default: "all 0.5s ease",
|
||||
},
|
||||
"bar-border-radius": {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
spacing: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
"text-align": {
|
||||
type: String,
|
||||
default: "center", // 'left', 'right'
|
||||
},
|
||||
"text-position": {
|
||||
type: String,
|
||||
default: "bottom", // 'bottom', 'top', 'middle', 'inside'
|
||||
},
|
||||
"font-size": {
|
||||
type: Number,
|
||||
default: 13,
|
||||
},
|
||||
"text-fg-color": {
|
||||
type: String,
|
||||
default: "#222",
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pct() {
|
||||
var pct = (this.val / this.max) * 100;
|
||||
pct = pct.toFixed(2);
|
||||
return Math.min(pct, this.max);
|
||||
},
|
||||
size_px() {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
return 2;
|
||||
case "small":
|
||||
return 4;
|
||||
case "medium":
|
||||
return 8;
|
||||
case "large":
|
||||
return 12;
|
||||
case "big":
|
||||
return 16;
|
||||
case "huge":
|
||||
return 32;
|
||||
case "massive":
|
||||
return 64;
|
||||
}
|
||||
|
||||
return isNumber(this.size) ? this.size : 32;
|
||||
},
|
||||
text_padding() {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
case "small":
|
||||
case "medium":
|
||||
case "large":
|
||||
case "big":
|
||||
case "huge":
|
||||
case "massive":
|
||||
return Math.min(Math.max(Math.ceil(this.size_px / 8), 3), 12);
|
||||
}
|
||||
|
||||
return isNumber(this.spacing) ? this.spacing : 4;
|
||||
},
|
||||
text_font_size() {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
case "small":
|
||||
case "medium":
|
||||
case "large":
|
||||
case "big":
|
||||
case "huge":
|
||||
case "massive":
|
||||
return Math.min(Math.max(Math.ceil(this.size_px * 1.4), 11), 32);
|
||||
}
|
||||
|
||||
return isNumber(this.fontSize) ? this.fontSize : 13;
|
||||
},
|
||||
progress_style() {
|
||||
var style = {
|
||||
background: this.bgColor,
|
||||
};
|
||||
|
||||
if (this.textPosition == "middle" || this.textPosition == "inside") {
|
||||
style["position"] = "relative";
|
||||
style["min-height"] = this.size_px + "px";
|
||||
style["z-index"] = "-2";
|
||||
}
|
||||
|
||||
if (this.barBorderRadius > 0) {
|
||||
style["border-radius"] = this.barBorderRadius + "px";
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
bar_style() {
|
||||
var style = {
|
||||
background: this.barColor,
|
||||
width: this.pct + "%",
|
||||
height: this.size_px + "px",
|
||||
transition: this.barTransition,
|
||||
};
|
||||
|
||||
if (this.barBorderRadius > 0) {
|
||||
style["border-radius"] = this.barBorderRadius + "px";
|
||||
}
|
||||
|
||||
if (this.textPosition == "middle" || this.textPosition == "inside") {
|
||||
style["position"] = "absolute";
|
||||
style["top"] = "0";
|
||||
style["height"] = "100%";
|
||||
(style["min-height"] = this.size_px + "px"), (style["z-index"] = "-1");
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
text_style() {
|
||||
var style = {
|
||||
color: this.textFgColor,
|
||||
"font-size": this.text_font_size + "px",
|
||||
"text-align": this.textAlign,
|
||||
};
|
||||
|
||||
if (
|
||||
this.textPosition == "top" ||
|
||||
this.textPosition == "middle" ||
|
||||
this.textPosition == "inside"
|
||||
)
|
||||
style["padding-bottom"] = this.text_padding + "px";
|
||||
if (
|
||||
this.textPosition == "bottom" ||
|
||||
this.textPosition == "middle" ||
|
||||
this.textPosition == "inside"
|
||||
)
|
||||
style["padding-top"] = this.text_padding + "px";
|
||||
|
||||
return style;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
+288
-313
@@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<div id="search" @click="open" :class="{ active, ongoing, 'dark-mode': isDarkMode }">
|
||||
<!-- Search input section -->
|
||||
<div
|
||||
id="search"
|
||||
@click="open"
|
||||
v-bind:class="{ active, ongoing, 'dark-mode': isDarkMode }"
|
||||
>
|
||||
<div id="input">
|
||||
<!-- Close button visible when search is active -->
|
||||
<button
|
||||
v-if="active"
|
||||
class="action"
|
||||
@@ -12,9 +14,7 @@
|
||||
>
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
<!-- Search icon when search is not active -->
|
||||
<i v-else class="material-icons">search</i>
|
||||
<!-- Input field for search -->
|
||||
<input
|
||||
class="main-input"
|
||||
type="text"
|
||||
@@ -27,12 +27,11 @@
|
||||
:placeholder="$t('search.search')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search results for mobile -->
|
||||
<div v-if="isMobile && active" id="result" :class="{ hidden: !active }" ref="result">
|
||||
<div id="result-list">
|
||||
<div class="button" style="width: 100%">Search Context: {{ getContext }}</div>
|
||||
<!-- List of search results -->
|
||||
<div class="button" style="width: 100%">
|
||||
Search Context: {{ getContext }}
|
||||
</div>
|
||||
<ul v-show="results.length > 0">
|
||||
<li
|
||||
v-for="(s, k) in results"
|
||||
@@ -48,23 +47,20 @@
|
||||
<i v-else-if="s.archive" class="material-icons archive-icons"> archive </i>
|
||||
<i v-else class="material-icons file-icons"> insert_drive_file </i>
|
||||
<span class="text-container">
|
||||
{{ basePath(s.path, s.dir) }}<b>{{ baseName(s.path) }}</b>
|
||||
{{ basePath(s.path,s.dir) }}<b>{{ baseName(s.path) }}</b>
|
||||
</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- 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 v-show="isEmpty && !isRunning">
|
||||
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
||||
<p>{{ noneMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isEmpty">
|
||||
<!-- Reset filters button -->
|
||||
<template v-if="isEmpty">
|
||||
<button
|
||||
class="mobile-boxes"
|
||||
v-if="value.length === 0 && !showBoxes"
|
||||
@@ -72,8 +68,7 @@
|
||||
>
|
||||
Reset filters
|
||||
</button>
|
||||
<!-- Box types when no search input is present -->
|
||||
<div v-if="value.length === 0 && showBoxes">
|
||||
<template v-if="value.length === 0 && showBoxes">
|
||||
<div class="boxes">
|
||||
<h3>{{ $t("search.types") }}</h3>
|
||||
<div>
|
||||
@@ -91,18 +86,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search results for desktop -->
|
||||
<div v-show="!isMobile && active" id="result-desktop" ref="result">
|
||||
<div class="searchContext">Search Context: {{ getContext }}</div>
|
||||
<div id="result-list">
|
||||
<div>
|
||||
<div>
|
||||
<!-- Button groups for filtering search results -->
|
||||
<template>
|
||||
<p v-show="isEmpty && isRunning" id="renew">
|
||||
<i class="material-icons spin">autorenew</i>
|
||||
</p>
|
||||
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
||||
<p>{{ noneMessage }}</p>
|
||||
<div class="helpButton" @click="toggleHelp()">Help</div>
|
||||
</div>
|
||||
<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>
|
||||
<template>
|
||||
<ButtonGroup
|
||||
:buttons="folderSelect"
|
||||
@button-clicked="addToTypes"
|
||||
@@ -116,8 +138,7 @@
|
||||
@remove-button-clicked="removeFromTypes"
|
||||
: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
|
||||
@@ -140,42 +161,8 @@
|
||||
<p>MB</p>
|
||||
</div>
|
||||
</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 -->
|
||||
</template>
|
||||
</template>
|
||||
<ul v-show="results.length > 0">
|
||||
<li
|
||||
v-for="(s, k) in results"
|
||||
@@ -191,7 +178,7 @@
|
||||
<i v-else-if="s.archive" class="material-icons archive-icons"> archive </i>
|
||||
<i v-else class="material-icons file-icons"> insert_drive_file </i>
|
||||
<span class="text-container">
|
||||
{{ basePath(s.path, s.dir) }}<b>{{ baseName(s.path) }}</b>
|
||||
{{ basePath(s.path,s.dir) }}<b>{{ baseName(s.path) }}</b>
|
||||
</span>
|
||||
</router-link>
|
||||
</li>
|
||||
@@ -200,250 +187,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ButtonGroup from "./ButtonGroup.vue";
|
||||
import { search } from "@/api";
|
||||
import { getters, mutations, state } from "@/store";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
var boxes = {
|
||||
folder: { label: "folders", icon: "folder" },
|
||||
file: { label: "files", icon: "insert_drive_file" },
|
||||
archive: { label: "archives", icon: "archive" },
|
||||
image: { label: "images", icon: "photo" },
|
||||
audio: { label: "audio files", icon: "volume_up" },
|
||||
video: { label: "videos", icon: "movie" },
|
||||
doc: { label: "documents", icon: "picture_as_pdf" },
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ButtonGroup,
|
||||
},
|
||||
name: "search",
|
||||
data: function () {
|
||||
return {
|
||||
largerThan: "",
|
||||
smallerThan: "",
|
||||
noneMessage: "Start typing 3 or more characters to begin searching.",
|
||||
searchTypes: "",
|
||||
isTypeSelectDisabled: false,
|
||||
showHelp: false,
|
||||
folderSelect: [
|
||||
{ label: "Only Folders", value: "type:folder" },
|
||||
{ label: "Only Files", value: "type:file" },
|
||||
],
|
||||
typeSelect: [
|
||||
{ label: "Photos", value: "type:image" },
|
||||
{ label: "Audio", value: "type:audio" },
|
||||
{ label: "Videos", value: "type:video" },
|
||||
{ label: "Documents", value: "type:doc" },
|
||||
{ label: "Archives", value: "type:archive" },
|
||||
],
|
||||
value: "",
|
||||
ongoing: false,
|
||||
results: [],
|
||||
reload: false,
|
||||
scrollable: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
largerThan() {
|
||||
this.submit();
|
||||
},
|
||||
smallerThan() {
|
||||
this.submit();
|
||||
},
|
||||
searchTypes() {
|
||||
this.submit();
|
||||
},
|
||||
active(active) {
|
||||
const resultList = document.getElementById("result-list");
|
||||
if (!active) {
|
||||
resultList.classList.remove("active");
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
resultList.classList.add("active");
|
||||
}, 100);
|
||||
},
|
||||
currentPrompt(val, old) {
|
||||
this.active = val?.prompt === "search";
|
||||
if (old?.prompt === "search" && !this.active) {
|
||||
if (this.reload) {
|
||||
this.setReload(true);
|
||||
}
|
||||
|
||||
document.body.style.overflow = "auto";
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
this.value = "";
|
||||
this.active = false;
|
||||
this.$refs.input.blur();
|
||||
} else if (this.active) {
|
||||
this.reload = false;
|
||||
this.$refs.input.focus();
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
},
|
||||
value() {
|
||||
if (this.results.length) {
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return state.isMobile;
|
||||
},
|
||||
foldersOnly() {
|
||||
return this.isTypeSelectDisabled;
|
||||
},
|
||||
active() {
|
||||
return getters.currentPromptName() == "search";
|
||||
},
|
||||
showOverlay() {
|
||||
return getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
|
||||
},
|
||||
isDarkMode() {
|
||||
return getters.isDarkMode();
|
||||
},
|
||||
showBoxes() {
|
||||
return this.searchTypes == "";
|
||||
},
|
||||
boxes() {
|
||||
return boxes;
|
||||
},
|
||||
isEmpty() {
|
||||
return this.results.length === 0;
|
||||
},
|
||||
text() {
|
||||
if (this.ongoing) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return this.value === ""
|
||||
? this.$t("search.typeToSearch")
|
||||
: this.$t("search.pressToSearch");
|
||||
},
|
||||
isRunning() {
|
||||
return this.ongoing;
|
||||
},
|
||||
searchHelp() {
|
||||
return this.showHelp;
|
||||
},
|
||||
getContext() {
|
||||
let path = state.route.path;
|
||||
path = path.slice(1);
|
||||
path = "./" + path.substring(path.indexOf("/") + 1);
|
||||
path = path.replace(/\/+$/, "") + "/";
|
||||
return path;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async navigateTo(url) {
|
||||
mutations.closeHovers();
|
||||
await this.$nextTick();
|
||||
setTimeout(() => this.$router.push(url), 10);
|
||||
},
|
||||
basePath(str, isDir) {
|
||||
let parts = str.replace(/(\/$|^\/)/, "").split("/");
|
||||
if (parts.length <= 1) {
|
||||
if (isDir) {
|
||||
return "/";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
parts.pop();
|
||||
parts = parts.join("/") + "/";
|
||||
if (isDir) {
|
||||
parts = "/" + parts; // fix weird rtl thing
|
||||
}
|
||||
return parts;
|
||||
},
|
||||
baseName(str) {
|
||||
let parts = str.replace(/(\/$|^\/)/, "").split("/");
|
||||
return parts.pop();
|
||||
},
|
||||
open() {
|
||||
mutations.showHover("search");
|
||||
},
|
||||
close(event) {
|
||||
this.value = "";
|
||||
event.stopPropagation();
|
||||
mutations.closeHovers();
|
||||
},
|
||||
keyup(event) {
|
||||
if (event.keyCode === 27) {
|
||||
this.close(event);
|
||||
return;
|
||||
}
|
||||
this.results.length === 0;
|
||||
},
|
||||
addToTypes(string) {
|
||||
if (this.searchTypes.includes(string)) {
|
||||
return true;
|
||||
}
|
||||
if (string == null || string == "") {
|
||||
return false;
|
||||
}
|
||||
this.searchTypes = this.searchTypes + string + " ";
|
||||
},
|
||||
resetSearchFilters() {
|
||||
this.searchTypes = "";
|
||||
},
|
||||
removeFromTypes(string) {
|
||||
if (string == null || string == "") {
|
||||
return false;
|
||||
}
|
||||
this.searchTypes = this.searchTypes.replace(string + " ", "");
|
||||
if (state.isMobile) {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
folderSelectClicked() {
|
||||
this.isTypeSelectDisabled = true; // Disable the other ButtonGroup
|
||||
},
|
||||
resetButtonGroups() {
|
||||
this.isTypeSelectDisabled = false;
|
||||
},
|
||||
async submit(event) {
|
||||
this.showHelp = false;
|
||||
if (event != undefined) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (this.value === "" || this.value.length < 3) {
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
this.noneMessage = "Not enough characters to search (min 3)";
|
||||
return;
|
||||
}
|
||||
let searchTypesFull = this.searchTypes;
|
||||
if (this.largerThan != "") {
|
||||
searchTypesFull = searchTypesFull + "type:largerThan=" + this.largerThan + " ";
|
||||
}
|
||||
if (this.smallerThan != "") {
|
||||
searchTypesFull = searchTypesFull + "type:smallerThan=" + this.smallerThan + " ";
|
||||
}
|
||||
let path = state.route.path;
|
||||
this.ongoing = true;
|
||||
try {
|
||||
this.results = await search(path, searchTypesFull + this.value);
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
}
|
||||
this.ongoing = false;
|
||||
if (this.results.length == 0) {
|
||||
this.noneMessage = "No results found in indexed search.";
|
||||
}
|
||||
},
|
||||
toggleHelp() {
|
||||
this.showHelp = !this.showHelp;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.main-input {
|
||||
@@ -457,7 +200,6 @@ export default {
|
||||
color: white;
|
||||
border-left: 1px solid gray;
|
||||
border-right: 1px solid gray;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#result-desktop > #result-list {
|
||||
@@ -522,8 +264,8 @@ export default {
|
||||
|
||||
/* Search */
|
||||
#search {
|
||||
background-color: unset !important;
|
||||
z-index: 5;
|
||||
background-color: unset;
|
||||
z-index: 3;
|
||||
position: fixed;
|
||||
top: 0.5em;
|
||||
min-width: 35em;
|
||||
@@ -643,10 +385,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);
|
||||
@@ -773,3 +511,240 @@ body.rtl #search .boxes h3 {
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ButtonGroup from "./ButtonGroup.vue";
|
||||
import { mapState, mapGetters, mapMutations } from "vuex";
|
||||
import { search } from "@/api";
|
||||
import { darkMode } from "@/utils/constants";
|
||||
|
||||
var boxes = {
|
||||
folder: { label: "folders", icon: "folder" },
|
||||
file: { label: "files", icon: "insert_drive_file" },
|
||||
archive: { label: "archives", icon: "archive" },
|
||||
image: { label: "images", icon: "photo" },
|
||||
audio: { label: "audio files", icon: "volume_up" },
|
||||
video: { label: "videos", icon: "movie" },
|
||||
doc: { label: "documents", icon: "picture_as_pdf" },
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ButtonGroup,
|
||||
},
|
||||
name: "search",
|
||||
data: function () {
|
||||
return {
|
||||
largerThan: "",
|
||||
smallerThan: "",
|
||||
noneMessage: "Start typing 3 or more characters to begin searching.",
|
||||
searchTypes: "",
|
||||
isTypeSelectDisabled: false,
|
||||
showHelp: false,
|
||||
folderSelect: [
|
||||
{ label: "Only Folders", value: "type:folder" },
|
||||
{ label: "Only Files", value: "type:file" },
|
||||
],
|
||||
typeSelect: [
|
||||
{ label: "Photos", value: "type:image" },
|
||||
{ label: "Audio", value: "type:audio" },
|
||||
{ label: "Videos", value: "type:video" },
|
||||
{ label: "Documents", value: "type:doc" },
|
||||
{ label: "Archives", value: "type:archive" },
|
||||
],
|
||||
value: "",
|
||||
width: window.innerWidth,
|
||||
active: false,
|
||||
ongoing: false,
|
||||
results: [],
|
||||
reload: false,
|
||||
scrollable: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
active(active) {
|
||||
const resultList = document.getElementById("result-list");
|
||||
if (!active) {
|
||||
resultList.classList.remove("active");
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
resultList.classList.add("active");
|
||||
}, 100);
|
||||
},
|
||||
show(val, old) {
|
||||
this.active = val === "search";
|
||||
if (old === "search" && !this.active) {
|
||||
if (this.reload) {
|
||||
this.setReload(true);
|
||||
}
|
||||
|
||||
document.body.style.overflow = "auto";
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
this.value = "";
|
||||
this.active = false;
|
||||
this.$refs.input.blur();
|
||||
} else if (this.active) {
|
||||
this.reload = false;
|
||||
this.$refs.input.focus();
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
},
|
||||
value() {
|
||||
if (this.results.length) {
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user", "show"]),
|
||||
...mapGetters(["isListing"]),
|
||||
isDarkMode() {
|
||||
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode") ? this.user.darkMode : darkMode;
|
||||
},
|
||||
showBoxes() {
|
||||
return this.searchTypes == "";
|
||||
},
|
||||
boxes() {
|
||||
return boxes;
|
||||
},
|
||||
isEmpty() {
|
||||
return this.results.length === 0;
|
||||
},
|
||||
text() {
|
||||
if (this.ongoing) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return this.value === ""
|
||||
? this.$t("search.typeToSearch")
|
||||
: this.$t("search.pressToSearch");
|
||||
},
|
||||
isMobile() {
|
||||
return this.width <= 800;
|
||||
},
|
||||
isRunning() {
|
||||
return this.ongoing;
|
||||
},
|
||||
searchHelp() {
|
||||
return this.showHelp;
|
||||
},
|
||||
getContext() {
|
||||
let path = this.$route.path
|
||||
path = path.slice(1);
|
||||
path = "./" + path.substring(path.indexOf("/") + 1);
|
||||
path = path.replace(/\/+$/, "") + "/";
|
||||
return path
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
this.handleResize(); // Call this once to set the initial width
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.width = window.innerWidth;
|
||||
},
|
||||
async navigateTo(url) {
|
||||
this.closeHovers();
|
||||
await this.$nextTick();
|
||||
setTimeout(() => this.$router.push(url), 0);
|
||||
},
|
||||
basePath(str,isDir) {
|
||||
let parts = str.replace(/(\/$|^\/)/, "").split("/");
|
||||
if (parts.length <= 1) {
|
||||
if (isDir) {
|
||||
return "/"
|
||||
}
|
||||
return "";
|
||||
}
|
||||
console.log("basePath",parts)
|
||||
parts.pop();
|
||||
parts = parts.join("/") + "/";
|
||||
if (isDir) {
|
||||
parts = "/" + parts; // fix weird rtl thing
|
||||
}
|
||||
return parts;
|
||||
},
|
||||
baseName(str) {
|
||||
let parts = str.replace(/(\/$|^\/)/, "").split("/");
|
||||
return parts.pop();
|
||||
},
|
||||
...mapMutations(["showHover", "closeHovers", "setReload"]),
|
||||
open() {
|
||||
this.showHover("search");
|
||||
},
|
||||
close(event) {
|
||||
event.stopPropagation();
|
||||
this.closeHovers();
|
||||
},
|
||||
keyup(event) {
|
||||
if (event.keyCode === 27) {
|
||||
this.close(event);
|
||||
return;
|
||||
}
|
||||
this.results.length === 0;
|
||||
},
|
||||
addToTypes(string) {
|
||||
if (this.searchTypes.includes(string)) {
|
||||
return true;
|
||||
}
|
||||
if (string == null || string == "") {
|
||||
return false;
|
||||
}
|
||||
this.searchTypes = this.searchTypes + string + " ";
|
||||
},
|
||||
resetSearchFilters() {
|
||||
this.searchTypes = "";
|
||||
},
|
||||
removeFromTypes(string) {
|
||||
if (string == null || string == "") {
|
||||
return false;
|
||||
}
|
||||
this.searchTypes = this.searchTypes.replace(string + " ", "");
|
||||
if (this.isMobile) {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
folderSelectClicked() {
|
||||
this.isTypeSelectDisabled = true; // Disable the other ButtonGroup
|
||||
},
|
||||
resetButtonGroups() {
|
||||
this.isTypeSelectDisabled = false;
|
||||
},
|
||||
async submit(event) {
|
||||
this.showHelp = false;
|
||||
event.preventDefault();
|
||||
if (this.value === "" || this.value.length < 3) {
|
||||
this.ongoing = false;
|
||||
this.results = [];
|
||||
this.noneMessage = "Not enough characters to search (min 3)";
|
||||
return;
|
||||
}
|
||||
let searchTypesFull = this.searchTypes;
|
||||
if (this.largerThan != "") {
|
||||
searchTypesFull = searchTypesFull + "type:largerThan=" + this.largerThan + " ";
|
||||
}
|
||||
if (this.smallerThan != "") {
|
||||
searchTypesFull = searchTypesFull + "type:smallerThan=" + this.smallerThan + " ";
|
||||
}
|
||||
let path = this.$route.path;
|
||||
this.ongoing = true;
|
||||
try {
|
||||
this.results = await search(path, searchTypesFull + this.value);
|
||||
} catch (error) {
|
||||
this.$showError(error);
|
||||
}
|
||||
this.ongoing = false;
|
||||
if (this.results.length == 0) {
|
||||
this.noneMessage = "No results found in indexed search.";
|
||||
}
|
||||
},
|
||||
toggleHelp() {
|
||||
this.showHelp = !this.showHelp;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
+114
-344
@@ -1,57 +1,18 @@
|
||||
<template>
|
||||
<nav
|
||||
id="sidebar"
|
||||
:class="{ active: active, 'dark-mode': isDarkMode, sticky: user?.stickySidebar }"
|
||||
>
|
||||
<div class="card">
|
||||
<div class="card-wrapper">
|
||||
<nav :class="{ active, 'dark-mode': isDarkMode }">
|
||||
<template v-if="isLogged">
|
||||
<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>
|
||||
<div v-if="user.perm.create">
|
||||
<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>
|
||||
|
||||
<!-- Section for logged-in users -->
|
||||
<div v-if="isLoggedIn" class="sidebar-scroll-list">
|
||||
<!-- Buttons visible if user has create permission -->
|
||||
<div v-if="user.perm?.create">
|
||||
<!-- New Folder button -->
|
||||
<button
|
||||
@click="showHover('newDir')"
|
||||
@click="$store.commit('showHover', 'newDir')"
|
||||
class="action"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
@@ -59,9 +20,8 @@
|
||||
<i class="material-icons">create_new_folder</i>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
<!-- New File button -->
|
||||
<button
|
||||
@click="showHover('newFile')"
|
||||
@click="$store.commit('showHover', 'newFile')"
|
||||
class="action"
|
||||
:aria-label="$t('sidebar.newFile')"
|
||||
:title="$t('sidebar.newFile')"
|
||||
@@ -69,26 +29,22 @@
|
||||
<i class="material-icons">note_add</i>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Settings and Logout buttons -->
|
||||
<div>
|
||||
<!-- Settings button -->
|
||||
<button
|
||||
class="action"
|
||||
@click="navigateTo('/settings/global')"
|
||||
@click="toSettings"
|
||||
:aria-label="$t('sidebar.settings')"
|
||||
:title="$t('sidebar.settings')"
|
||||
>
|
||||
<i class="material-icons">settings_applications</i>
|
||||
<span>{{ $t("sidebar.settings") }}</span>
|
||||
</button>
|
||||
<!-- Logout button -->
|
||||
|
||||
<button
|
||||
v-if="canLogout"
|
||||
@click="logout"
|
||||
@@ -101,31 +57,8 @@
|
||||
<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>
|
||||
|
||||
<!-- Section for non-logged-in users -->
|
||||
<div v-else class="sidebar-scroll-list">
|
||||
<!-- Login button -->
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link
|
||||
class="action"
|
||||
to="/login"
|
||||
@@ -135,7 +68,6 @@
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>{{ $t("sidebar.login") }}</span>
|
||||
</router-link>
|
||||
<!-- Signup button, if signup is enabled -->
|
||||
<router-link
|
||||
v-if="signup"
|
||||
class="action"
|
||||
@@ -146,11 +78,17 @@
|
||||
<i class="material-icons">person_add</i>
|
||||
<span>{{ $t("sidebar.signup") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="buffer"></div>
|
||||
<div class="credits">
|
||||
<span>
|
||||
</template>
|
||||
<div
|
||||
class="credits"
|
||||
v-if="$router.currentRoute.path.includes('/files/') && !disableUsedPercentage"
|
||||
>
|
||||
<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"
|
||||
@@ -166,8 +104,9 @@
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import * as upload from "@/utils/upload";
|
||||
import * as auth from "@/utils/auth";
|
||||
import {
|
||||
version,
|
||||
@@ -177,285 +116,116 @@ import {
|
||||
noAuth,
|
||||
loginPage,
|
||||
} from "@/utils/constants";
|
||||
import { files, users } from "@/api";
|
||||
import ProgressBar from "@/components/ProgressBar.vue";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { showError } from "@/notify";
|
||||
import { files as api } from "@/api";
|
||||
import ProgressBar from "vue-simple-progress";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import { darkMode } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: "sidebar",
|
||||
components: {
|
||||
ProgressBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hoverText: "Quick Toggles", // Initially empty
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (getters.isLoggedIn()) {
|
||||
this.updateUsage();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return getters.isMobile();
|
||||
},
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
user() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return {};
|
||||
}
|
||||
return state.user;
|
||||
},
|
||||
...mapState(["user"]),
|
||||
isDarkMode() {
|
||||
return getters.isDarkMode();
|
||||
},
|
||||
isLoggedIn() {
|
||||
return getters.isLoggedIn();
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode") ? this.user.darkMode : darkMode;
|
||||
},
|
||||
...mapGetters(["isLogged"]),
|
||||
active() {
|
||||
return getters.isSidebarVisible();
|
||||
return this.$store.state.show === "sidebar";
|
||||
},
|
||||
signup: () => signup,
|
||||
version: () => version,
|
||||
disableExternal: () => disableExternal,
|
||||
disableUsedPercentage: () => disableUsedPercentage,
|
||||
canLogout: () => !noAuth && loginPage,
|
||||
usage: () => state.usage,
|
||||
route: () => state.route,
|
||||
},
|
||||
watch: {
|
||||
route() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
if (!state.user.stickySidebar) {
|
||||
mutations.closeSidebar();
|
||||
}
|
||||
asyncComputed: {
|
||||
usage: {
|
||||
async get() {
|
||||
let path = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
: this.$route.path + "/";
|
||||
let usageStats = { used: 0, total: 0, usedPercentage: 0 };
|
||||
if (this.disableUsedPercentage) {
|
||||
return usageStats;
|
||||
}
|
||||
try {
|
||||
let usage = await api.usage(path);
|
||||
usageStats = {
|
||||
used: prettyBytes(usage.used / 1024, { binary: true }),
|
||||
total: prettyBytes(usage.total / 1024, { binary: true }),
|
||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||
};
|
||||
} catch (error) {
|
||||
this.$showError(error);
|
||||
}
|
||||
return usageStats;
|
||||
},
|
||||
default: { used: "0 B", total: "0 B", usedPercentage: 0 },
|
||||
shouldUpdate() {
|
||||
return this.$router.currentRoute.path.includes("/files/");
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateHoverText(text) {
|
||||
this.hoverText = text;
|
||||
toRoot() {
|
||||
this.$router.push({ path: "/files/" }, () => {});
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
resetHoverTextToDefault() {
|
||||
this.hoverText = "Quick Toggles"; // Reset to default hover text
|
||||
toSettings() {
|
||||
this.$router.push({ path: "/settings" }, () => {});
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
toggleClick() {
|
||||
mutations.updateUser({ singleClick: !state.user.singleClick });
|
||||
help() {
|
||||
this.$store.commit("showHover", "help");
|
||||
},
|
||||
toggleDarkMode() {
|
||||
mutations.toggleDarkMode();
|
||||
upload: function () {
|
||||
if (
|
||||
typeof window.DataTransferItem !== "undefined" &&
|
||||
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
|
||||
) {
|
||||
this.$store.commit("showHover", "upload");
|
||||
} else {
|
||||
document.getElementById("upload-input").click();
|
||||
}
|
||||
},
|
||||
toggleSticky() {
|
||||
let newSettings = state.user;
|
||||
newSettings.stickySidebar = !state.user.stickySidebar;
|
||||
users.update(newSettings, ["stickySidebar"]);
|
||||
},
|
||||
async updateUsage() {
|
||||
if (!getters.isLoggedIn()) {
|
||||
uploadInput(event) {
|
||||
this.$store.commit("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 = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
: this.$route.path + "/";
|
||||
let conflict = upload.checkConflict(files, this.req.items);
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace",
|
||||
confirm: (event) => {
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
upload.handleFiles(files, path, true);
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
mutations.setUsage(usageStats);
|
||||
|
||||
upload.handleFiles(files, path);
|
||||
},
|
||||
showHover(value) {
|
||||
return mutations.showHover(value);
|
||||
},
|
||||
navigateTo(path) {
|
||||
this.$router.push({ path: path }, () => {});
|
||||
mutations.closeHovers();
|
||||
},
|
||||
// Show the help overlay
|
||||
help() {
|
||||
mutations.showHover("help");
|
||||
},
|
||||
uploadFunc() {
|
||||
mutations.showHover("upload");
|
||||
},
|
||||
// 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>
|
||||
@@ -11,19 +11,17 @@
|
||||
@wheel="wheelMove"
|
||||
>
|
||||
<img
|
||||
v-if="!isTiff"
|
||||
:src="src"
|
||||
src=""
|
||||
class="image-ex-img image-ex-img-center"
|
||||
ref="imgex"
|
||||
@load="onLoad"
|
||||
/>
|
||||
<canvas v-else ref="imgex" class="image-ex-img"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import throttle from "@/utils/throttle";
|
||||
import { showError } from "@/notify";
|
||||
import throttle from "lodash.throttle";
|
||||
import UTIF from "utif";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
src: String,
|
||||
@@ -57,18 +55,15 @@ export default {
|
||||
},
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
isTiff: false, // Determine if the image is a TIFF
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isTiff = this.checkIfTiff(this.src);
|
||||
if (this.isTiff) {
|
||||
this.decodeTiff(this.src);
|
||||
} else {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
let container = this.$refs.container;
|
||||
this.classList.forEach((className) => container.classList.add(className));
|
||||
// set width and height if they are zero
|
||||
if (getComputedStyle(container).width === "0px") {
|
||||
container.style.width = "100%";
|
||||
}
|
||||
@@ -84,10 +79,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
src: function () {
|
||||
this.isTiff = this.checkIfTiff(this.src);
|
||||
if (this.isTiff) {
|
||||
this.decodeTiff(this.src);
|
||||
} else {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
|
||||
@@ -97,29 +89,19 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checkIfTiff(src) {
|
||||
// Modified from UTIF.replaceIMG
|
||||
decodeUTIF() {
|
||||
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
||||
const suff = src.split(".").pop().toLowerCase();
|
||||
return sufs.includes(suff);
|
||||
},
|
||||
async decodeTiff(src) {
|
||||
try {
|
||||
const response = await fetch(src);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const blob = await response.blob(); // Convert response to a blob
|
||||
const imgex = this.$refs.imgex;
|
||||
|
||||
if (imgex) {
|
||||
// Create a URL for the blob and set it as the image source
|
||||
imgex.src = URL.createObjectURL(blob);
|
||||
imgex.onload = () => URL.revokeObjectURL(imgex.src); // Clean up URL object after loading
|
||||
}
|
||||
} catch (error) {
|
||||
showError("Error decoding TIFF");
|
||||
console.error("Error decoding TIFF:", error);
|
||||
}
|
||||
let suff = document.location.pathname.split(".").pop().toLowerCase();
|
||||
if (sufs.indexOf(suff) == -1) return false;
|
||||
let xhr = new XMLHttpRequest();
|
||||
UTIF._xhrs.push(xhr);
|
||||
UTIF._imgs.push(this.$refs.imgex);
|
||||
xhr.open("GET", this.src);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = UTIF._imgLoaded;
|
||||
xhr.send();
|
||||
return true;
|
||||
},
|
||||
onLoad() {
|
||||
let img = this.$refs.imgex;
|
||||
@@ -164,7 +146,9 @@ export default {
|
||||
let container = this.$refs.container;
|
||||
let img = this.$refs.imgex;
|
||||
|
||||
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2);
|
||||
this.position.center.x = Math.floor(
|
||||
(container.clientWidth - img.clientWidth) / 2
|
||||
);
|
||||
this.position.center.y = Math.floor(
|
||||
(container.clientHeight - img.clientHeight) / 2
|
||||
);
|
||||
@@ -291,7 +275,6 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.image-ex-container {
|
||||
margin: auto;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{ activebutton: this.isMaximized && this.isSelected }"
|
||||
:class="{ activebutton: this.isMaximized && this.isSelected}"
|
||||
class="item"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -18,17 +18,13 @@
|
||||
@click="toggleClick"
|
||||
:class="{ activetitle: this.isMaximized && this.isSelected }"
|
||||
>
|
||||
<img
|
||||
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
|
||||
v-lazy="thumbnailUrl"
|
||||
:class="{ activeimg: this.isMaximized && this.isSelected }"
|
||||
ref="thumbnail"
|
||||
/>
|
||||
<i
|
||||
:class="{ iconActive: this.isMaximized && this.isSelected }"
|
||||
v-else
|
||||
class="material-icons"
|
||||
></i>
|
||||
<img
|
||||
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
|
||||
v-lazy="thumbnailUrl"
|
||||
:class="{ activeimg: this.isMaximized && this.isSelected }"
|
||||
ref="thumbnail"
|
||||
/>
|
||||
<i :class="{ iconActive: this.isMaximized && this.isSelected }" v-else class="material-icons"></i>
|
||||
</div>
|
||||
|
||||
<div :class="{ activecontent: this.isMaximized && this.isSelected }">
|
||||
@@ -68,10 +64,10 @@
|
||||
<script>
|
||||
import { enableThumbs } from "@/utils/constants";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { fromNow } from "@/utils/moment";
|
||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "item",
|
||||
@@ -94,44 +90,32 @@ export default {
|
||||
"path",
|
||||
],
|
||||
computed: {
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
selected() {
|
||||
return state.selected;
|
||||
},
|
||||
req() {
|
||||
return state.req;
|
||||
},
|
||||
jwt() {
|
||||
return state.jwt;
|
||||
},
|
||||
selectedCount() {
|
||||
return getters.selectedCount();
|
||||
},
|
||||
...mapState(["user", "selected", "req", "jwt"]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
isClicked() {
|
||||
if (state.user.singleClick || !this.allowedView) {
|
||||
if (this.user.singleClick || !this.allowedView ) {
|
||||
return false;
|
||||
}
|
||||
// Assuming toggleClick returns a boolean value
|
||||
return !this.isMaximized;
|
||||
},
|
||||
allowedView() {
|
||||
return state.user.viewMode != "gallery" && state.user.viewMode != "normal";
|
||||
return this.user.viewMode != "gallery" && this.user.viewMode != "normal"
|
||||
},
|
||||
singleClick() {
|
||||
return this.readOnly == undefined && state.user.singleClick;
|
||||
return this.readOnly == undefined && this.user.singleClick;
|
||||
},
|
||||
isSelected() {
|
||||
return this.selected.indexOf(this.index) !== -1;
|
||||
},
|
||||
isDraggable() {
|
||||
return this.readOnly == undefined && state.user.perm?.rename;
|
||||
return this.readOnly == undefined && this.user.perm.rename;
|
||||
},
|
||||
canDrop() {
|
||||
if (!this.isDir || this.readOnly !== undefined) return false;
|
||||
|
||||
for (let i of this.selected) {
|
||||
if (state.req.items[i].url === this.url) {
|
||||
if (this.req.items[i].url === this.url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -139,12 +123,12 @@ export default {
|
||||
return true;
|
||||
},
|
||||
thumbnailUrl() {
|
||||
let path = state.req.path;
|
||||
if (state.req.path == "/") {
|
||||
path = "";
|
||||
let path = this.req.path
|
||||
if (this.req.path == "/") {
|
||||
path = ""
|
||||
}
|
||||
const file = {
|
||||
path: path + "/" + this.name,
|
||||
path: path +"/"+this.name,
|
||||
modified: this.modified,
|
||||
};
|
||||
|
||||
@@ -160,7 +144,7 @@ export default {
|
||||
mounted() {
|
||||
const observer = new IntersectionObserver(this.handleIntersect, {
|
||||
root: null,
|
||||
rootMargin: "0px",
|
||||
rootMargin: '0px',
|
||||
threshold: 0.5, // Adjust threshold as needed
|
||||
});
|
||||
|
||||
@@ -172,7 +156,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleIntersect(entries, observer) {
|
||||
entries.forEach((entry) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.isThumbnailInView = true;
|
||||
// Stop observing once thumbnail is in view
|
||||
@@ -183,26 +167,27 @@ export default {
|
||||
toggleClick() {
|
||||
this.isMaximized = this.isClicked;
|
||||
},
|
||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
||||
humanSize: function () {
|
||||
return this.type == "invalid_link"
|
||||
? "invalid link"
|
||||
: getHumanReadableFilesize(this.size);
|
||||
},
|
||||
humanTime: function () {
|
||||
if (this.readOnly == undefined && state.user.dateFormat) {
|
||||
return fromNow(this.modified, state.user.locale).format("L LT");
|
||||
if (this.readOnly == undefined && this.user.dateFormat) {
|
||||
return moment(this.modified).format("L LT");
|
||||
}
|
||||
return fromNow(this.modified, state.user.locale);
|
||||
return moment(this.modified).fromNow();
|
||||
},
|
||||
dragStart: function () {
|
||||
if (getters.selectedCount() === 0) {
|
||||
mutations.addSelected(this.index);
|
||||
if (this.selectedCount === 0) {
|
||||
this.addSelected(this.index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isSelected) {
|
||||
mutations.resetSelected();
|
||||
mutations.addSelected(this.index);
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
}
|
||||
},
|
||||
dragOver: function (event) {
|
||||
@@ -223,7 +208,7 @@ export default {
|
||||
if (!this.canDrop) return;
|
||||
event.preventDefault();
|
||||
|
||||
if (getters.selectedCount() === 0) return;
|
||||
if (this.selectedCount === 0) return;
|
||||
|
||||
let el = event.target;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
@@ -236,9 +221,9 @@ export default {
|
||||
|
||||
for (let i of this.selected) {
|
||||
items.push({
|
||||
from: state.req.items[i].url,
|
||||
to: this.url + encodeURIComponent(state.req.items[i].name),
|
||||
name: state.req.items[i].name,
|
||||
from: this.req.items[i].url,
|
||||
to: this.url + encodeURIComponent(this.req.items[i].name),
|
||||
name: this.req.items[i].name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,9 +235,9 @@ export default {
|
||||
api
|
||||
.move(items, overwrite, rename)
|
||||
.then(() => {
|
||||
mutations.setReload(true);
|
||||
this.$store.commit("setReload", true);
|
||||
})
|
||||
.catch(showError);
|
||||
.catch(this.$showError);
|
||||
};
|
||||
|
||||
let conflict = upload.checkConflict(items, baseItems);
|
||||
@@ -261,14 +246,14 @@ export default {
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
mutations.showHover({
|
||||
name: "replace-rename",
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
@@ -279,11 +264,11 @@ export default {
|
||||
action(overwrite, rename);
|
||||
},
|
||||
itemClick: function (event) {
|
||||
if (this.singleClick && !state.multiple) this.open();
|
||||
if (this.singleClick && !this.$store.state.multiple) this.open();
|
||||
else this.click(event);
|
||||
},
|
||||
click: function (event) {
|
||||
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
|
||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
||||
|
||||
setTimeout(() => {
|
||||
this.touches = 0;
|
||||
@@ -294,8 +279,8 @@ export default {
|
||||
this.open();
|
||||
}
|
||||
|
||||
if (state.selected.indexOf(this.index) !== -1) {
|
||||
mutations.removeSelected(this.index);
|
||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
||||
this.removeSelected(this.index);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,16 +297,21 @@ export default {
|
||||
}
|
||||
|
||||
for (; fi <= la; fi++) {
|
||||
if (state.selected.indexOf(fi) == -1) {
|
||||
mutations.addSelected(fi);
|
||||
if (this.$store.state.selected.indexOf(fi) == -1) {
|
||||
this.addSelected(fi);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
|
||||
mutations.resetSelected();
|
||||
mutations.addSelected(this.index);
|
||||
if (
|
||||
!this.singleClick &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!this.$store.state.multiple
|
||||
)
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
},
|
||||
open: function () {
|
||||
this.$router.push({ path: this.url });
|
||||
|
||||
@@ -7,22 +7,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mutations } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "action",
|
||||
props: ["icon", "label", "counter", "show"],
|
||||
methods: {
|
||||
action: function () {
|
||||
if (this.show) {
|
||||
mutations.showHover(this.show);
|
||||
this.$store.commit("showHover", this.show);
|
||||
}
|
||||
|
||||
this.$emit("action");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<header>
|
||||
<slot />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { logoURL } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: "header-bar",
|
||||
props: ["showLogo", "showMenu"],
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
logoURL,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleSidebar() {
|
||||
if ( this.$store.state.show == "sidebar" ) {
|
||||
this.$store.commit("closeHovers");
|
||||
} else {
|
||||
this.$store.commit("showHover", "sidebar");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -6,53 +6,36 @@
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.copyMessage") }}</p>
|
||||
<file-list ref="fileList" @update:selected="(val) => (dest = val)"> </file-list>
|
||||
<file-list @update:selected="(val) => (dest = val)"></file-list>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="card-action"
|
||||
style="display: flex; align-items: center; justify-content: space-between"
|
||||
>
|
||||
<template v-if="user.perm.create">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="$refs.fileList.createDir()"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
style="justify-self: left"
|
||||
>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<div>
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="copy"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')"
|
||||
>
|
||||
{{ $t("buttons.copy") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="copy"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')"
|
||||
>
|
||||
{{ $t("buttons.copy") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mutations, state } from "@/store";
|
||||
import FileList from "./FileList.vue";
|
||||
import { mapState } from "vuex";
|
||||
import FileList from "./FileList";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "copy",
|
||||
@@ -63,14 +46,7 @@ export default {
|
||||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
},
|
||||
computed: mapState(["req", "selected"]),
|
||||
methods: {
|
||||
copy: async function (event) {
|
||||
event.preventDefault();
|
||||
@@ -79,9 +55,9 @@ export default {
|
||||
// Create a new promise for each file.
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: store.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(store.req.items[item].name),
|
||||
name: store.req.items[item].name,
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,8 +69,9 @@ export default {
|
||||
.then(() => {
|
||||
buttons.success("copy");
|
||||
|
||||
if (state.route.path === this.dest) {
|
||||
mutations.setReload(true);
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit("setReload", true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,12 +79,12 @@ export default {
|
||||
})
|
||||
.catch((e) => {
|
||||
buttons.done("copy");
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
});
|
||||
};
|
||||
|
||||
if (state.route.path === this.dest) {
|
||||
mutations.closeHovers();
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit("closeHovers");
|
||||
action(false, true);
|
||||
|
||||
return;
|
||||
@@ -120,14 +97,14 @@ export default {
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
mutations.showHover({
|
||||
name: "replace-rename",
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
@@ -30,59 +30,49 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "delete",
|
||||
computed: {
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
selectedCount() {
|
||||
return getters.selectedCount();
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
...mapGetters(["isListing", "selectedCount"]),
|
||||
...mapState(["req", "selected", "showConfirm"]),
|
||||
},
|
||||
methods: {
|
||||
closeHovers() {
|
||||
mutations.closeHovers();
|
||||
},
|
||||
async submit() {
|
||||
...mapMutations(["closeHovers"]),
|
||||
submit: async function () {
|
||||
buttons.loading("delete");
|
||||
|
||||
try {
|
||||
if (!this.isListing) {
|
||||
await api.remove(state.route.path);
|
||||
await api.remove(this.$route.path);
|
||||
buttons.success("delete");
|
||||
|
||||
this.currentPrompt?.confirm();
|
||||
this.showConfirm();
|
||||
this.closeHovers();
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeHovers();
|
||||
|
||||
if (getters.selectedCount() === 0) {
|
||||
if (this.selectedCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let index of state.selected) {
|
||||
promises.push(api.remove(state.req.items[index].url));
|
||||
for (let index of this.selected) {
|
||||
promises.push(api.remove(this.req.items[index].url));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
buttons.success("delete");
|
||||
mutations.setReload(true); // Handle reload as needed
|
||||
this.$store.commit("setReload", true);
|
||||
} catch (e) {
|
||||
buttons.done("delete");
|
||||
showError(e);
|
||||
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||
this.$showError(e);
|
||||
if (this.isListing) this.$store.commit("setReload", true);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-content">
|
||||
<p>Are you sure you want to delete this user?</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
v-focus
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button class="button button--flat" @click="deleteUser">
|
||||
{{ $t("buttons.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { users as api } from "@/api";
|
||||
import { showSuccess,showError } from "@/notify";
|
||||
import buttons from "@/utils/buttons";
|
||||
import { state, mutations, getters } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "delete",
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
user() {
|
||||
return this.currentPrompt?.props?.user;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deleteUser(event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await api.remove(this.user.id);
|
||||
this.$router.push({ path: "/settings/users" });
|
||||
showSuccess(this.$t("settings.userDeleted"));
|
||||
} catch (e) {
|
||||
e.message === "403"
|
||||
? showError(this.$t("errors.forbidden"), false)
|
||||
: showError(e);
|
||||
}
|
||||
},
|
||||
closeHovers() {
|
||||
mutations.closeHovers();
|
||||
},
|
||||
submit: async function () {
|
||||
buttons.loading("delete");
|
||||
|
||||
try {
|
||||
if (!this.isListing) {
|
||||
await api.remove(this.$route.path);
|
||||
buttons.success("delete");
|
||||
|
||||
this.currentPrompt?.confirm();
|
||||
this.closeHovers();
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeHovers();
|
||||
|
||||
if (getters.selectedCount() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let index of this.selected) {
|
||||
promises.push(api.remove(state.req.items[index].url));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
buttons.success("delete");
|
||||
mutations.setReload(true); // Handle reload as needed
|
||||
} catch (e) {
|
||||
buttons.done("delete");
|
||||
showError(e);
|
||||
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -3,6 +3,7 @@
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("prompts.download") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.downloadMessage") }}</p>
|
||||
|
||||
@@ -10,7 +11,7 @@
|
||||
v-for="(ext, format) in formats"
|
||||
:key="format"
|
||||
class="button button--block"
|
||||
@click="currentPrompt.confirm(format)"
|
||||
@click="showConfirm(format)"
|
||||
v-focus
|
||||
>
|
||||
{{ ext }}
|
||||
@@ -20,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "download",
|
||||
@@ -37,10 +38,6 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
},
|
||||
computed: mapState(["showConfirm"]),
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="searchContext">Path: {{ nav }}</div>
|
||||
<ul class="file-list">
|
||||
<li
|
||||
@click="itemClick"
|
||||
@@ -17,14 +16,18 @@
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
{{ $t("prompts.currentlyNavigating") }} <code>{{ nav }}</code
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { state, mutations } from "@/store";
|
||||
import { mapState } from "vuex";
|
||||
import url from "@/utils/url";
|
||||
import { files } from "@/api";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "file-list",
|
||||
@@ -40,12 +43,13 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "user"]),
|
||||
nav() {
|
||||
return decodeURIComponent(this.current);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fillOptions(state.req);
|
||||
this.fillOptions(this.req);
|
||||
},
|
||||
methods: {
|
||||
fillOptions(req) {
|
||||
@@ -86,7 +90,7 @@ export default {
|
||||
// content.
|
||||
let uri = event.currentTarget.dataset.url;
|
||||
|
||||
files.fetch(uri).then(this.fillOptions).catch(showError);
|
||||
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
|
||||
},
|
||||
touchstart(event) {
|
||||
let url = event.currentTarget.dataset.url;
|
||||
@@ -114,7 +118,7 @@ export default {
|
||||
}
|
||||
},
|
||||
itemClick: function (event) {
|
||||
if (state.user.singleClick) this.next(event);
|
||||
if (this.user.singleClick) this.next(event);
|
||||
else this.select(event);
|
||||
},
|
||||
select: function (event) {
|
||||
@@ -129,17 +133,6 @@ export default {
|
||||
this.selected = event.currentTarget.dataset.url;
|
||||
this.$emit("update:selected", this.selected);
|
||||
},
|
||||
createDir: async function () {
|
||||
mutations.showHover({
|
||||
name: "newDir",
|
||||
action: null,
|
||||
confirm: null,
|
||||
props: {
|
||||
redirect: false,
|
||||
base: this.current === state.route.path ? null : this.current,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
type="submit"
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')"
|
||||
@@ -33,14 +33,5 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mutations } from "@/store"; // Import the mutations
|
||||
|
||||
export default {
|
||||
name: "help",
|
||||
computed: {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers; // Return the closeHovers mutation
|
||||
},
|
||||
},
|
||||
};
|
||||
export default { name: "help" };
|
||||
</script>
|
||||
@@ -33,25 +33,33 @@
|
||||
<p>
|
||||
<strong>MD5: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'md5')">{{ $t("prompts.show") }}</a></code
|
||||
><a @click="checksum($event, 'md5')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<strong>SHA1: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'sha1')">{{ $t("prompts.show") }}</a></code
|
||||
><a @click="checksum($event, 'sha1')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<strong>SHA256: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'sha256')">{{ $t("prompts.show") }}</a></code
|
||||
><a @click="checksum($event, 'sha256')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<strong>SHA512: </strong
|
||||
><code
|
||||
><a @click="checksum($event, 'sha512')">{{ $t("prompts.show") }}</a></code
|
||||
><a @click="checksum($event, 'sha512')">{{
|
||||
$t("prompts.show")
|
||||
}}</a></code
|
||||
>
|
||||
</p>
|
||||
</template>
|
||||
@@ -60,7 +68,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
type="submit"
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')"
|
||||
@@ -70,87 +78,73 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { formatTimestamp } from "@/utils/moment";
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "info",
|
||||
computed: {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers;
|
||||
},
|
||||
req() {
|
||||
return state.req;
|
||||
},
|
||||
selected() {
|
||||
return state.selected;
|
||||
},
|
||||
selectedCount() {
|
||||
return getters.selectedCount();
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
humanSize() {
|
||||
if (getters.selectedCount() === 0 || !this.isListing) {
|
||||
return getHumanReadableFilesize(state.req.size);
|
||||
...mapState(["req", "selected"]),
|
||||
...mapGetters(["selectedCount", "isListing"]),
|
||||
humanSize: function () {
|
||||
if (this.selectedCount === 0 || !this.isListing) {
|
||||
return getHumanReadableFilesize(this.req.size);
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
|
||||
for (let selected of this.selected) {
|
||||
sum += state.req.items[selected].size;
|
||||
sum += this.req.items[selected].size;
|
||||
}
|
||||
|
||||
return getHumanReadableFilesize(sum);
|
||||
},
|
||||
humanTime() {
|
||||
if (getters.selectedCount() === 0) {
|
||||
return formatTimestamp(state.req.modified, state.user.locale);
|
||||
humanTime: function () {
|
||||
if (this.selectedCount === 0) {
|
||||
return moment(this.req.modified).fromNow();
|
||||
}
|
||||
return formatTimestamp(
|
||||
state.req.items[this.selected[0]].modified,
|
||||
state.user.locale
|
||||
);
|
||||
|
||||
return moment(this.req.items[this.selected[0]].modified).fromNow();
|
||||
},
|
||||
modTime() {
|
||||
return new Date(Date.parse(state.req.modified)).toLocaleString();
|
||||
modTime: function () {
|
||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
||||
},
|
||||
name() {
|
||||
return getters.selectedCount() === 0
|
||||
? state.req.name
|
||||
: state.req.items[this.selected[0]].name;
|
||||
name: function () {
|
||||
return this.selectedCount === 0
|
||||
? this.req.name
|
||||
: this.req.items[this.selected[0]].name;
|
||||
},
|
||||
dir() {
|
||||
dir: function () {
|
||||
return (
|
||||
getters.selectedCount() > 1 ||
|
||||
(getters.selectedCount() === 0
|
||||
? state.req.isDir
|
||||
: state.req.items[this.selected[0]].isDir)
|
||||
this.selectedCount > 1 ||
|
||||
(this.selectedCount === 0
|
||||
? this.req.isDir
|
||||
: this.req.items[this.selected[0]].isDir)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async checksum(event, algo) {
|
||||
checksum: async function (event, algo) {
|
||||
event.preventDefault();
|
||||
|
||||
let link;
|
||||
|
||||
if (getters.selectedCount()) {
|
||||
link = state.req.items[this.selected[0]].url;
|
||||
if (this.selectedCount) {
|
||||
link = this.req.items[this.selected[0]].url;
|
||||
} else {
|
||||
link = state.route.path;
|
||||
link = this.$route.path;
|
||||
}
|
||||
|
||||
try {
|
||||
const hash = await api.checksum(link, algo);
|
||||
event.target.innerHTML = hash;
|
||||
// eslint-disable-next-line
|
||||
event.target.innerHTML = hash
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,54 +5,37 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<file-list ref="fileList" @update:selected="(val) => (dest = val)"> </file-list>
|
||||
<file-list @update:selected="(val) => (dest = val)"></file-list>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="card-action"
|
||||
style="display: flex; align-items: center; justify-content: space-between"
|
||||
>
|
||||
<template v-if="user.perm.create">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="$refs.fileList.createDir()"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
style="justify-self: left"
|
||||
>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<div>
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="move"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')"
|
||||
>
|
||||
{{ $t("buttons.move") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="move"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')"
|
||||
>
|
||||
{{ $t("buttons.move") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mutations, state } from "@/store";
|
||||
import FileList from "./FileList.vue";
|
||||
import { mapState } from "vuex";
|
||||
import FileList from "./FileList";
|
||||
import { files as api } from "@/api";
|
||||
import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "move",
|
||||
@@ -63,39 +46,32 @@ export default {
|
||||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
},
|
||||
computed: mapState(["req", "selected"]),
|
||||
methods: {
|
||||
move: async function (event) {
|
||||
event.preventDefault();
|
||||
let items = [];
|
||||
|
||||
for (let item of state.selected) {
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: state.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(state.req.items[item].name),
|
||||
name: state.req.items[item].name,
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name,
|
||||
});
|
||||
}
|
||||
|
||||
let action = async (overwrite, rename) => {
|
||||
buttons.loading("move");
|
||||
|
||||
await api
|
||||
.move(items, overwrite, rename)
|
||||
.then(() => {
|
||||
buttons.success("move");
|
||||
this.$router.push({ path: this.dest });
|
||||
mutations.setReload(true);
|
||||
})
|
||||
.catch((e) => {
|
||||
buttons.done("move");
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -106,16 +82,15 @@ export default {
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
mutations.showHover({
|
||||
name: "replace-rename",
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
mutations.setReload(true);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
@@ -35,50 +35,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import url from "@/utils/url";
|
||||
import { getters, mutations, state } from "@/store"; // Import your custom store
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "new-dir",
|
||||
props: {
|
||||
redirect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
base: {
|
||||
type: [String, null],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
data: function () {
|
||||
return {
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
},
|
||||
methods: {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
async submit(event) {
|
||||
submit: async function (event) {
|
||||
event.preventDefault();
|
||||
if (this.name === "") return;
|
||||
if (this.new === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri;
|
||||
if (this.base) uri = this.base;
|
||||
else if (getters.isFiles()) uri = state.route.path + "/";
|
||||
else uri = "/";
|
||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
uri = url.removeLastDir(uri) + "/";
|
||||
@@ -89,17 +68,12 @@ export default {
|
||||
|
||||
try {
|
||||
await api.post(uri);
|
||||
if (this.redirect) {
|
||||
this.$router.push({ path: uri });
|
||||
} else if (!this.base) {
|
||||
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
||||
mutations.updateRequest(res);
|
||||
}
|
||||
this.$router.push({ path: uri });
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
@@ -35,36 +35,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { state } from "@/store";
|
||||
import { mapGetters } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import url from "@/utils/url";
|
||||
import { getters, mutations } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "new-file",
|
||||
data() {
|
||||
data: function () {
|
||||
return {
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers;
|
||||
},
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
},
|
||||
methods: {
|
||||
async submit(event) {
|
||||
submit: async function (event) {
|
||||
event.preventDefault();
|
||||
if (this.name === "") return;
|
||||
// Build the path of the new file.
|
||||
let uri = getters.isFiles() ? state.route.path + "/" : "/";
|
||||
if (this.new === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
uri = url.removeLastDir(uri) + "/";
|
||||
@@ -77,10 +70,10 @@ export default {
|
||||
await api.post(uri);
|
||||
this.$router.push({ path: uri });
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<component
|
||||
v-if="showOverlay"
|
||||
:ref="currentPromptName"
|
||||
:is="currentPromptName"
|
||||
v-bind="currentPrompt.props"
|
||||
/>
|
||||
<component ref="currentComponent" :is="currentComponent"></component>
|
||||
<div v-if="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Help from "./Help.vue";
|
||||
import Info from "./Info.vue";
|
||||
import Delete from "./Delete.vue";
|
||||
import Rename from "./Rename.vue";
|
||||
import Download from "./Download.vue";
|
||||
import Move from "./Move.vue";
|
||||
import Copy from "./Copy.vue";
|
||||
import NewFile from "./NewFile.vue";
|
||||
import NewDir from "./NewDir.vue";
|
||||
import Replace from "./Replace.vue";
|
||||
import ReplaceRename from "./ReplaceRename.vue";
|
||||
import Share from "./Share.vue";
|
||||
import Upload from "./Upload.vue";
|
||||
import ShareDelete from "./ShareDelete.vue";
|
||||
import DeleteUser from "./DeleteUser.vue";
|
||||
import Sidebar from "../Sidebar.vue";
|
||||
import Help from "./Help";
|
||||
import Info from "./Info";
|
||||
import Delete from "./Delete";
|
||||
import Rename from "./Rename";
|
||||
import Download from "./Download";
|
||||
import Move from "./Move";
|
||||
import Copy from "./Copy";
|
||||
import NewFile from "./NewFile";
|
||||
import NewDir from "./NewDir";
|
||||
import Replace from "./Replace";
|
||||
import ReplaceRename from "./ReplaceRename";
|
||||
import Share from "./Share";
|
||||
import Upload from "./Upload";
|
||||
import ShareDelete from "./ShareDelete";
|
||||
import { mapState } from "vuex";
|
||||
import buttons from "@/utils/buttons";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "prompts",
|
||||
@@ -46,34 +40,31 @@ export default {
|
||||
ReplaceRename,
|
||||
Upload,
|
||||
ShareDelete,
|
||||
Sidebar,
|
||||
DeleteUser,
|
||||
},
|
||||
data() {
|
||||
data: function () {
|
||||
return {
|
||||
pluginData: {
|
||||
buttons,
|
||||
store: state, // Directly use state
|
||||
store: this.$store,
|
||||
router: this.$router,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
let currentPrompt = getters.currentPrompt();
|
||||
if (!currentPrompt) return;
|
||||
if (this.show == null) return;
|
||||
|
||||
let prompt = this.$refs[currentPrompt.name];
|
||||
let prompt = this.$refs.currentComponent;
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
event.stopImmediatePropagation();
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (event.keyCode === 13) {
|
||||
switch (currentPrompt.name) {
|
||||
if (event.keyCode == 13) {
|
||||
switch (this.show) {
|
||||
case "delete":
|
||||
prompt.submit();
|
||||
break;
|
||||
@@ -91,27 +82,38 @@ export default {
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
currentPromptName() {
|
||||
if (getters.currentPromptName() == null) {
|
||||
return "";
|
||||
}
|
||||
return getters.currentPromptName();
|
||||
...mapState(["show", "plugins"]),
|
||||
currentComponent: function () {
|
||||
const matched =
|
||||
[
|
||||
"info",
|
||||
"help",
|
||||
"delete",
|
||||
"rename",
|
||||
"move",
|
||||
"copy",
|
||||
"newFile",
|
||||
"newDir",
|
||||
"download",
|
||||
"replace",
|
||||
"replace-rename",
|
||||
"share",
|
||||
"upload",
|
||||
"share-delete",
|
||||
].indexOf(this.show) >= 0;
|
||||
|
||||
return (matched && this.show) || null;
|
||||
},
|
||||
currentPrompt() {
|
||||
if (getters.currentPrompt() == null) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
plugins() {
|
||||
return state.plugins;
|
||||
},
|
||||
showOverlay() {
|
||||
return getters.currentPromptName() !== "more";
|
||||
showOverlay: function () {
|
||||
return (
|
||||
this.show !== null && this.show !== "more"
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetPrompts() {
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
@@ -39,14 +39,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import url from "@/utils/url";
|
||||
import { files as api } from "@/api";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "rename",
|
||||
data() {
|
||||
data: function () {
|
||||
return {
|
||||
name: "",
|
||||
};
|
||||
@@ -55,48 +56,37 @@ export default {
|
||||
this.name = this.oldName();
|
||||
},
|
||||
computed: {
|
||||
req() {
|
||||
return state.req;
|
||||
},
|
||||
selected() {
|
||||
return state.selected;
|
||||
},
|
||||
selectedCount() {
|
||||
return state.selectedCount;
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers;
|
||||
},
|
||||
...mapState(["req", "selected", "selectedCount"]),
|
||||
...mapGetters(["isListing"]),
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
mutations.closeHovers();
|
||||
cancel: function () {
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
oldName() {
|
||||
oldName: function () {
|
||||
if (!this.isListing) {
|
||||
return state.req.name;
|
||||
return this.req.name;
|
||||
}
|
||||
|
||||
if (getters.selectedCount() === 0 || getters.selectedCount() > 1) {
|
||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
||||
// This shouldn't happen.
|
||||
return;
|
||||
}
|
||||
|
||||
return state.req.items[this.selected[0]].name;
|
||||
return this.req.items[this.selected[0]].name;
|
||||
},
|
||||
async submit() {
|
||||
submit: async function () {
|
||||
let oldLink = "";
|
||||
let newLink = "";
|
||||
|
||||
if (!this.isListing) {
|
||||
oldLink = state.req.url;
|
||||
oldLink = this.req.url;
|
||||
} else {
|
||||
oldLink = state.req.items[this.selected[0]].url;
|
||||
oldLink = this.req.items[this.selected[0]].url;
|
||||
}
|
||||
|
||||
newLink = url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||
newLink =
|
||||
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||
|
||||
try {
|
||||
await api.move([{ from: oldLink, to: newLink }]);
|
||||
@@ -105,12 +95,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
mutations.setReload(true);
|
||||
this.$store.commit("setReload", true);
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
mutations.closeHovers();
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="currentPrompt.action"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
@@ -19,7 +19,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
@click="currentPrompt.confirm"
|
||||
@click="showConfirm"
|
||||
:aria-label="$t('buttons.replace')"
|
||||
:title="$t('buttons.replace')"
|
||||
>
|
||||
@@ -28,15 +28,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "replace",
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt(); // Access the getter directly from the store
|
||||
},
|
||||
},
|
||||
computed: mapState(["showConfirm"]),
|
||||
};
|
||||
</script>
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="(event) => currentPrompt.confirm(event, 'rename')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
@@ -19,7 +19,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--blue"
|
||||
@click="(event) => currentPrompt.confirm(event, 'overwrite')"
|
||||
@click="(event) => showConfirm(event, 'rename')"
|
||||
:aria-label="$t('buttons.rename')"
|
||||
:title="$t('buttons.rename')"
|
||||
>
|
||||
@@ -38,14 +38,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "replace-rename",
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt(); // Access the getter directly from the store
|
||||
},
|
||||
},
|
||||
computed: mapState(["showConfirm"]),
|
||||
};
|
||||
</script>
|
||||
@@ -3,7 +3,6 @@
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("buttons.share") }}</h2>
|
||||
</div>
|
||||
<div class="searchContext">Path: {{ getContext }}</div>
|
||||
|
||||
<template v-if="listing">
|
||||
<div class="card-content">
|
||||
@@ -18,7 +17,9 @@
|
||||
<tr v-for="link in links" :key="link.hash">
|
||||
<td>{{ link.hash }}</td>
|
||||
<td>
|
||||
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
|
||||
<template v-if="link.expire !== 0">{{
|
||||
humanTime(link.expire)
|
||||
}}</template>
|
||||
<template v-else>{{ $t("permanent") }}</template>
|
||||
</td>
|
||||
<td class="small">
|
||||
@@ -58,7 +59,7 @@
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')"
|
||||
>
|
||||
@@ -95,7 +96,11 @@
|
||||
</select>
|
||||
</div>
|
||||
<p>{{ $t("prompts.optionalPassword") }}</p>
|
||||
<input class="input input--block" type="password" v-model.trim="password" />
|
||||
<input
|
||||
class="input input--block"
|
||||
type="password"
|
||||
v-model.trim="password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
@@ -119,16 +124,16 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { showSuccess, showError } from "@/notify";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import { share as api, pub as pub_api } from "@/api";
|
||||
import { fromNow } from "@/utils/moment";
|
||||
import moment from "moment";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
export default {
|
||||
name: "share",
|
||||
data() {
|
||||
data: function () {
|
||||
return {
|
||||
time: "",
|
||||
unit: "hours",
|
||||
@@ -139,37 +144,19 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers;
|
||||
},
|
||||
req() {
|
||||
return state.req; // Access state directly
|
||||
},
|
||||
selected() {
|
||||
return state.selected; // Access state directly
|
||||
},
|
||||
selectedCount() {
|
||||
return state.selected.length; // Compute selectedCount directly from state
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing(); // Access getter directly from the store
|
||||
},
|
||||
...mapState(["req", "selected", "selectedCount"]),
|
||||
...mapGetters(["isListing"]),
|
||||
url() {
|
||||
if (!this.isListing) {
|
||||
return state.route.path;
|
||||
return this.$route.path;
|
||||
}
|
||||
if (getters.selectedCount() !== 1) {
|
||||
// selecting current view image
|
||||
return state.route.path;
|
||||
|
||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
||||
// This shouldn't happen.
|
||||
return;
|
||||
}
|
||||
return state.req.items[this.selected[0]].url;
|
||||
},
|
||||
getContext() {
|
||||
let path = state.route.path.replace("/files/", "./");
|
||||
if (getters.selectedCount() === 1) {
|
||||
path = path + state.req.items[this.selected[0]].name;
|
||||
}
|
||||
return path;
|
||||
|
||||
return this.req.items[this.selected[0]].url;
|
||||
},
|
||||
},
|
||||
async beforeMount() {
|
||||
@@ -178,25 +165,25 @@ export default {
|
||||
this.links = links;
|
||||
this.sort();
|
||||
|
||||
if (this.links.length === 0) {
|
||||
if (this.links.length == 0) {
|
||||
this.listing = false;
|
||||
}
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.clip = new Clipboard(".copy-clipboard");
|
||||
this.clip.on("success", () => {
|
||||
showSuccess(this.$t("success.linkCopied"));
|
||||
this.$showSuccess(this.$t("success.linkCopied"));
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clip.destroy();
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
let isPermanent = !this.time || this.time === 0;
|
||||
submit: async function () {
|
||||
let isPermanent = !this.time || this.time == 0;
|
||||
|
||||
try {
|
||||
let res = null;
|
||||
@@ -216,30 +203,32 @@ export default {
|
||||
|
||||
this.listing = true;
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
async deleteLink(event, link) {
|
||||
deleteLink: async function (event, link) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await api.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
|
||||
if (this.links.length === 0) {
|
||||
if (this.links.length == 0) {
|
||||
this.listing = false;
|
||||
}
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
this.$showError(e);
|
||||
}
|
||||
},
|
||||
humanTime(time) {
|
||||
return fromNow(time, state.user.locale);
|
||||
return moment(time * 1000).fromNow();
|
||||
},
|
||||
buildLink(share) {
|
||||
return api.getShareURL(share);
|
||||
},
|
||||
hasDownloadLink() {
|
||||
return this.selected.length === 1 && !state.req.items[this.selected[0]].isDir;
|
||||
return (
|
||||
this.selected.length === 1 && !this.req.items[this.selected[0]].isDir
|
||||
);
|
||||
},
|
||||
buildDownloadLink(share) {
|
||||
return pub_api.getDownloadURL(share);
|
||||
@@ -252,9 +241,8 @@ export default {
|
||||
});
|
||||
},
|
||||
switchListing() {
|
||||
if (this.links.length === 0 && !this.listing) {
|
||||
// Access the store directly if needed
|
||||
mutations.closeHovers();
|
||||
if (this.links.length == 0 && !this.listing) {
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
this.listing = !this.listing;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
@click="closeHovers"
|
||||
@click="$store.commit('closeHovers')"
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
@@ -23,22 +23,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "share-delete",
|
||||
computed: {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
...mapState(["showConfirm"]),
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.currentPrompt?.confirm();
|
||||
submit: function () {
|
||||
this.showConfirm();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,63 @@
|
||||
<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 { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "uploadFiles",
|
||||
data: function () {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["filesInUpload", "filesInUploadCount"]),
|
||||
},
|
||||
methods: {
|
||||
toggle: function () {
|
||||
this.open = !this.open;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,24 +1,17 @@
|
||||
<template>
|
||||
<select @change="change" :value="locale">
|
||||
<option v-for="(value, label) in locales" :key="label" :value="label">
|
||||
{{ $t("languages." + label) }}
|
||||
<select v-on:change="change" :value="locale">
|
||||
<option v-for="(language, value) in locales" :key="value" :value="value">
|
||||
{{ $t("languages." + language) }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Languages",
|
||||
props: {
|
||||
locale: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
export default {
|
||||
name: "languages",
|
||||
props: ["locale"],
|
||||
data() {
|
||||
return {
|
||||
let dataObj = {
|
||||
locales: {
|
||||
he: "he",
|
||||
hu: "hu",
|
||||
@@ -45,12 +38,18 @@ export default defineComponent({
|
||||
"zh-tw": "zhTW",
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(dataObj, "locales", {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return dataObj;
|
||||
},
|
||||
methods: {
|
||||
change(event) {
|
||||
const target = event.target;
|
||||
this.$emit("update:locale", target.value);
|
||||
this.$emit("update:locale", event.target.value);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -67,10 +67,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Languages from "./Languages.vue";
|
||||
import Rules from "./Rules.vue";
|
||||
import Permissions from "./Permissions.vue";
|
||||
import Commands from "./Commands.vue";
|
||||
import Languages from "./Languages";
|
||||
import Rules from "./Rules";
|
||||
import Permissions from "./Permissions";
|
||||
import Commands from "./Commands";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
@@ -118,4 +118,4 @@ export default {
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
@@ -57,7 +57,7 @@
|
||||
flex: 10 0 25em;
|
||||
}
|
||||
|
||||
.share__box__items #listingView.list .item {
|
||||
.share__box__items #listing.list .item {
|
||||
cursor: pointer;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
@@ -65,11 +65,11 @@
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.share__box__items #listingView.list .item .name {
|
||||
.share__box__items #listing.list .item .name {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.share__box__items #listingView.list .item .modified {
|
||||
.share__box__items #listing.list .item .modified {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user