Compare commits

...
27 changed files with 251 additions and 131 deletions

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
backend/**.db
backend/tmp
backend/vendor/**
frontend/dist/**
frontend/node_modules/**
+1
View File
@@ -9,6 +9,7 @@ rice-box.go
/backend/filebrowser
/backend/filebrowser.exe
/backend/backend.exe
/backend/tmp
/frontend/dist
/frontend/pkg
/frontend/test-results
+24 -5
View File
@@ -2,14 +2,33 @@
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.6.6-beta
**New Feature**:
- limit tar size creation to limit server burden. For example, don't let customers try to download the entire filesystem as a zip. see `server.maxArchiveSize` on config wiki.
**Notes**:
- disableUsedPercentage also hides text and source bar.
- share errors show up in logs in more verbose way.
- archive creation occurs on disk rather than in memory, use `server.cacheDir` to determine where temp files are stored.
- automatically ensures leading slash for scope
- https://github.com/gtsteffaniak/filebrowser/issues/472
- https://github.com/gtsteffaniak/filebrowser/issues/476
**BugFixes**:
- fix proxy user creation issue https://github.com/gtsteffaniak/filebrowser/issues/478
- externalUrl prefix issue fixed for shares. https://github.com/gtsteffaniak/filebrowser/issues/465
- fix File Opens Instead of Just Downloading https://github.com/gtsteffaniak/filebrowser/issues/480
- fix Download file name https://github.com/gtsteffaniak/filebrowser/issues/481
## v0.6.5-beta
**Notes**:
- added more share and download tests
**Notes**:
- added more share and download tests
**BugFixes**:
- fix share download issue https://github.com/gtsteffaniak/filebrowser/issues/465
- fix content length size calculation issue when downloading multiple files.
**BugFixes**:
- fix share download issue https://github.com/gtsteffaniak/filebrowser/issues/465
- fix content length size calculation issue when downloading multiple files.
## v0.6.4-beta
+1 -1
View File
@@ -163,7 +163,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
ShowHidden: a.Settings.UserDefaults.ShowHidden,
},
Username: a.Cred.Username,
Perm: a.Settings.UserDefaults.Perm,
Perm: a.Settings.UserDefaults.Permissions,
}
u = a.GetUser(d)
+12 -1
View File
@@ -5,6 +5,7 @@ import (
"path/filepath"
"github.com/gtsteffaniak/filebrowser/backend/logger"
"github.com/gtsteffaniak/filebrowser/backend/settings"
)
// UpdateFileMetadata updates the FileInfo for the specified directory in the index.
@@ -78,7 +79,17 @@ func GetIndex(name string) *Index {
defer indexesMutex.Unlock()
index, ok := indexes[name]
if !ok {
logger.Error(fmt.Sprintf("index %s not found", name))
// try path if name fails
// todo: update everywhere else so this isn't needed.
source, ok := settings.Config.Server.SourceMap[name]
if !ok {
logger.Error(fmt.Sprintf("index %s not found", name))
}
index, ok = indexes[source.Name]
if !ok {
logger.Error(fmt.Sprintf("index %s not found", name))
}
}
return index
}
+9 -5
View File
@@ -57,7 +57,9 @@ func withHashFileHelper(fn handleFunc) handleFunc {
}
}
data.path = strings.TrimSuffix(link.Path, "/") + "/" + strings.TrimPrefix(path, "/")
if path == "" || path == "/" {
data.path = link.Path
}
// Get file information with options
file, err := FileInfoFasterFunc(files.FileOptions{
Path: data.path,
@@ -67,7 +69,7 @@ func withHashFileHelper(fn handleFunc) handleFunc {
})
file.Token = link.Token
if err != nil {
logger.Error(fmt.Sprintf("error fetching file info for share %v %v %v", hash, path, err))
logger.Error(fmt.Sprintf("error fetching file info for share. hash=%v path=%v error=%v", hash, data.path, err))
return errToStatus(err), fmt.Errorf("error fetching share from server")
}
// Set the file info in the `data` object
@@ -115,13 +117,16 @@ func withUserHelper(fn handleFunc) handleFunc {
if err != nil {
return http.StatusInternalServerError, err
}
newUser := storage.CreateUser(users.User{
err = storage.CreateUser(users.User{
Username: proxyUser,
NonAdminEditable: users.NonAdminEditable{
Password: hashpass, // hashed password that can't actually be used
},
}, false)
data.user, err = store.Users.Get(newUser)
if err != nil {
return http.StatusInternalServerError, err
}
data.user, err = store.Users.Get(proxyUser)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -140,7 +145,6 @@ func withUserHelper(fn handleFunc) handleFunc {
return http.StatusUnauthorized, err
}
data.token = tokenString
var tk users.AuthToken
token, err := jwt.ParseWithClaims(tokenString, &tk, keyFunc)
if err != nil {
+11 -4
View File
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/gtsteffaniak/filebrowser/backend/files"
"github.com/gtsteffaniak/filebrowser/backend/logger"
"github.com/gtsteffaniak/filebrowser/backend/users"
_ "github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
@@ -19,7 +20,7 @@ import (
// @Tags Resources
// @Accept json
// @Produce json
// @Param files query string true "a list of files in the following format 'source::filename' and separated by '||' with additional items in the list. (required)"
// @Param files query string true "a list of files in the following format 'filename' and separated by '||' with additional items in the list. (required)"
// @Param inline query bool false "If true, sets 'Content-Disposition' to 'inline'. Otherwise, defaults to 'attachment'."
// @Param algo query string false "Compression algorithm for archiving multiple files or directories. Options: 'zip' and 'tar.gz'. Default is 'zip'."
// @Success 200 {file} file "Raw file or directory content, or archive for multiple files"
@@ -38,14 +39,20 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
fileInfo, ok := d.raw.(files.ExtendedFileInfo)
if !ok {
logger.Error("public share handler: failed to assert type files.FileInfo")
return http.StatusInternalServerError, fmt.Errorf("failed to assert type files.FileInfo")
}
prefix := fileInfo.Path
fileList := []string{}
for _, file := range strings.Split(f, "||") {
fileList = append(fileList, fileInfo.Source+"::"+prefix+file)
fileList = append(fileList, fileInfo.Source+"::"+fileInfo.Path+file)
}
return rawFilesHandler(w, r, d, fileList)
var status int
status, err = rawFilesHandler(w, r, d, fileList)
if err != nil {
logger.Error(fmt.Sprintf("public share handler: error processing filelist: %v", err))
return status, fmt.Errorf("error processing filelist: %v", f)
}
return status, nil
}
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
+62 -31
View File
@@ -3,7 +3,6 @@ package http
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"errors"
"fmt"
@@ -17,11 +16,12 @@ import (
"github.com/gtsteffaniak/filebrowser/backend/files"
"github.com/gtsteffaniak/filebrowser/backend/logger"
"github.com/gtsteffaniak/filebrowser/backend/settings"
"github.com/gtsteffaniak/filebrowser/backend/utils"
)
func setContentDisposition(w http.ResponseWriter, r *http.Request, fileName string) {
if r.URL.Query().Get("inline") == "true" {
w.Header().Set("Content-Disposition", "inline")
w.Header().Set("Content-Disposition", "inline; filename*=utf-8''"+url.PathEscape(fileName))
} else {
// As per RFC6266 section 4.3
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName))
@@ -205,15 +205,13 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
if err != nil {
return http.StatusInternalServerError, err
}
// Compute estimated download size
estimatedSize, err := computeArchiveSize(fileList, d)
if err != nil {
return http.StatusInternalServerError, err
}
// ** Single file download with Content-Length **
if len(fileList) == 1 && !isDir {
var estimatedSize int64
// Compute estimated download size
estimatedSize, err = computeArchiveSize(fileList, d)
if err != nil {
return http.StatusInternalServerError, err
}
fd, err2 := os.Open(realPath)
if err2 != nil {
return http.StatusInternalServerError, err2
@@ -244,6 +242,12 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
return 200, nil
}
if config.Server.MaxArchiveSizeGB > 0 {
maxSize := config.Server.MaxArchiveSizeGB * 1024 * 1024 * 1024
if estimatedSize > maxSize {
return http.StatusRequestEntityTooLarge, fmt.Errorf("pre-archive combined size of files exceeds maximum limit of %d GB", config.Server.MaxArchiveSizeGB)
}
}
// ** Archive (ZIP/TAR.GZ) handling **
algo := r.URL.Query().Get("algo")
var extension string
@@ -265,30 +269,47 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
}
fileName = url.PathEscape(baseDirName + extension)
var archiveData []byte
archiveData := filepath.Join(config.Server.CacheDir, utils.InsecureRandomIdentifier(10))
if extension == ".zip" {
archiveData, err = createZip(d, fileList...)
archiveData = archiveData + ".zip"
err = createZip(d, archiveData, fileList...)
} else {
archiveData, err = createTarGz(d, fileList...)
archiveData = archiveData + ".tar.gz"
err = createTarGz(d, archiveData, fileList...)
}
if err != nil {
return http.StatusInternalServerError, err
}
calculatedSize := len(archiveData)
sizeInMB := calculatedSize / 1024 / 1024
// if larger than 100 megabytes, log it
// stream archive to response
fd, err := os.Open(archiveData)
if err != nil {
return http.StatusInternalServerError, err
}
defer fd.Close()
// Get file size
fileInfo, err := fd.Stat()
if err != nil {
os.Remove(archiveData) // Remove the file if stat fails
return http.StatusInternalServerError, err
}
sizeInMB := fileInfo.Size() / 1024 / 1024
if sizeInMB > 100 {
logger.Debug(fmt.Sprintf("User %v is downloading large (%d MB) file: %v", d.user.Username, sizeInMB, fileName))
}
// Set headers AFTER computing actual archive size
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+fileName)
w.Header().Set("Content-Length", fmt.Sprintf("%d", calculatedSize))
w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
w.Header().Set("Content-Type", "application/octet-stream")
// Write archive data to response
_, err = w.Write(archiveData)
// Stream the file
_, err = io.Copy(w, fd)
os.Remove(archiveData) // Remove the file after streaming
if err != nil {
logger.Error(fmt.Sprintf("Failed to copy archive data to response: %v", err))
return http.StatusInternalServerError, err
}
@@ -329,36 +350,46 @@ func computeArchiveSize(fileList []string, d *requestContext) (int64, error) {
return estimatedSize, nil
}
func createZip(d *requestContext, filenames ...string) ([]byte, error) {
var buf bytes.Buffer
zipWriter := zip.NewWriter(&buf)
func createZip(d *requestContext, tmpDirPath string, filenames ...string) error {
file, err := os.Create(tmpDirPath)
if err != nil {
return err
}
defer file.Close()
zipWriter := zip.NewWriter(file)
defer zipWriter.Close()
for _, fname := range filenames {
err := addFile(fname, d, nil, zipWriter, false)
if err != nil {
logger.Error(fmt.Sprintf("Failed to add %s to ZIP: %v", fname, err))
return nil, err
return err
}
}
zipWriter.Close()
return buf.Bytes(), nil
return nil
}
func createTarGz(d *requestContext, filenames ...string) ([]byte, error) {
var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
func createTarGz(d *requestContext, tmpDirPath string, filenames ...string) error {
file, err := os.Create(tmpDirPath)
if err != nil {
return err
}
defer file.Close()
gzWriter := gzip.NewWriter(file)
defer gzWriter.Close()
tarWriter := tar.NewWriter(gzWriter)
defer tarWriter.Close()
for _, fname := range filenames {
err := addFile(fname, d, tarWriter, nil, false)
if err != nil {
logger.Error(fmt.Sprintf("Failed to add %s to TAR.GZ: %v", fname, err))
return nil, err
return err
}
}
tarWriter.Close()
gzWriter.Close()
return buf.Bytes(), nil
return nil
}
+6 -5
View File
@@ -39,7 +39,6 @@ func shareListHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
if err == errors.ErrNotExist {
return renderJSON(w, r, []*share.Link{})
}
if err != nil {
return http.StatusInternalServerError, err
}
@@ -174,14 +173,16 @@ func sharePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
stringHash = string(hash)
}
path := r.URL.Query().Get("path")
source := r.URL.Query().Get("source")
if source == "" {
source = config.Server.DefaultSource.Name
sourceName := r.URL.Query().Get("source")
if sourceName == "" {
sourceName = config.Server.DefaultSource.Name
}
source := config.Server.NameToSource[sourceName]
s = &share.Link{
Path: path,
Hash: secure_hash,
Source: source,
Source: source.Path,
Expire: expire,
UserID: d.user.ID,
PasswordHash: stringHash,
+6 -1
View File
@@ -126,7 +126,6 @@ func setupAuth() {
if Config.Auth.Method != "" {
logger.Warning("The `auth.method` setting is deprecated and will be removed in a future version. Please use `auth.methods` instead.")
}
Config.UserDefaults.Perm = Config.UserDefaults.Permissions
if Config.Auth.Methods.PasswordAuth.Enabled {
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "Password")
}
@@ -206,6 +205,8 @@ func setDefaults() Settings {
Database: "database.db",
SourceMap: map[string]Source{},
NameToSource: map[string]Source{},
MaxArchiveSizeGB: 50,
CacheDir: "tmp",
},
Auth: Auth{
AdminUsername: "admin",
@@ -234,6 +235,7 @@ func setDefaults() Settings {
ViewMode: "normal",
Locale: "en",
GallerySize: 3,
ThemeColor: "var(--blue)",
Permissions: users.Permissions{
Modify: false,
Share: false,
@@ -253,6 +255,9 @@ func ConvertToBackendScopes(scopes []users.SourceScope) ([]users.SourceScope, er
if scope.Scope == "" {
scope.Scope = "/"
}
if !strings.HasPrefix(scope.Scope, "/") {
scope.Scope = "/" + scope.Scope
}
// first check if its already a path name and keep it
source, ok := Config.Server.SourceMap[scope.Name]
if ok {
+6 -2
View File
@@ -44,9 +44,14 @@ func ApplyUserDefaults(u *users.User) {
u.Locale = Config.UserDefaults.Locale
u.ViewMode = Config.UserDefaults.ViewMode
u.SingleClick = Config.UserDefaults.SingleClick
u.Perm = Config.UserDefaults.Perm
u.Perm = Config.UserDefaults.Permissions
u.ShowHidden = Config.UserDefaults.ShowHidden
u.DateFormat = Config.UserDefaults.DateFormat
u.DisableOnlyOfficeExt = Config.UserDefaults.DisableOnlyOfficeExt
u.ThemeColor = Config.UserDefaults.ThemeColor
u.GallerySize = Config.UserDefaults.GallerySize
u.QuickDownload = Config.UserDefaults.QuickDownload
u.LockPassword = Config.UserDefaults.LockPassword
if len(u.Scopes) == 0 {
u.Scopes = []users.SourceScope{
{
@@ -55,5 +60,4 @@ func ApplyUserDefaults(u *users.User) {
},
}
}
}
+1 -1
View File
@@ -79,6 +79,7 @@ type Server struct {
ExternalUrl string `json:"externalUrl"`
InternalUrl string `json:"internalUrl"` // used by integrations
CacheDir string `json:"cacheDir"`
MaxArchiveSizeGB int64 `json:"maxArchiveSize"`
// not exposed to config
SourceMap map[string]Source // uses realpath as key
NameToSource map[string]Source // uses name as key
@@ -157,7 +158,6 @@ type UserDefaults struct {
ViewMode string `json:"viewMode"`
GallerySize int `json:"gallerySize"`
SingleClick bool `json:"singleClick"`
Perm users.Permissions `json:"perm"`
Permissions users.Permissions `json:"permissions"`
ShowHidden bool `json:"showHidden"`
DateFormat bool `json:"dateFormat"`
+1 -4
View File
@@ -1543,7 +1543,7 @@ const docTemplate = `{
"parameters": [
{
"type": "string",
"description": "a list of files in the following format 'source::filename' and separated by '||' with additional items in the list. (required)",
"description": "a list of files in the following format 'filename' and separated by '||' with additional items in the list. (required)",
"name": "files",
"in": "query",
"required": true
@@ -1831,9 +1831,6 @@ const docTemplate = `{
"lockPassword": {
"type": "boolean"
},
"perm": {
"$ref": "#/definitions/users.Permissions"
},
"permissions": {
"$ref": "#/definitions/users.Permissions"
},
+1 -4
View File
@@ -1532,7 +1532,7 @@
"parameters": [
{
"type": "string",
"description": "a list of files in the following format 'source::filename' and separated by '||' with additional items in the list. (required)",
"description": "a list of files in the following format 'filename' and separated by '||' with additional items in the list. (required)",
"name": "files",
"in": "query",
"required": true
@@ -1820,9 +1820,6 @@
"lockPassword": {
"type": "boolean"
},
"perm": {
"$ref": "#/definitions/users.Permissions"
},
"permissions": {
"$ref": "#/definitions/users.Permissions"
},
+2 -4
View File
@@ -148,8 +148,6 @@ definitions:
type: string
lockPassword:
type: boolean
perm:
$ref: '#/definitions/users.Permissions'
permissions:
$ref: '#/definitions/users.Permissions'
quickDownload:
@@ -1315,8 +1313,8 @@ paths:
description: Returns the raw content of a file, multiple files, or a directory.
Supports downloading files as archives in various formats.
parameters:
- description: a list of files in the following format 'source::filename' and
separated by '||' with additional items in the list. (required)
- description: a list of files in the following format 'filename' and separated
by '||' with additional items in the list. (required)
in: query
name: files
required: true
+28 -29
View File
@@ -1,8 +1,8 @@
import { fetchURL, adjustedData } from './utils'
import { getApiPath, extractSourceFromPath } from '@/utils/url.js'
import { getApiPath, extractSourceFromPath,removePrefix } from '@/utils/url.js'
import { state } from '@/store'
import { notify } from '@/notify'
import { externalUrl } from '@/utils/constants'
import { externalUrl,baseURL } from '@/utils/constants'
// Notify if errors occur
export async function fetchFiles(url, content = false) {
@@ -64,34 +64,32 @@ export function download(format, files) {
if (format !== 'zip') {
format = 'tar.gz'
}
try {
let fileargs = ''
if (files.length === 1) {
const result = extractSourceFromPath(decodeURI(files[0]))
fileargs = result.source + '::' + result.path + '||'
} else {
for (let file of files) {
const result = extractSourceFromPath(decodeURI(file))
fileargs += result.source + '::' + result.path + '||'
}
}
fileargs = fileargs.slice(0, -2) // remove trailing "||"
const apiPath = getApiPath('api/raw', {
files: encodeURIComponent(fileargs),
algo: format
})
const url = window.origin + apiPath
// Create a temporary <a> element to trigger the download
const link = document.createElement('a')
link.href = url
link.setAttribute('download', '') // Ensures it triggers a download
document.body.appendChild(link)
link.click()
document.body.removeChild(link) // Clean up
} catch (err) {
notify.showError(err.message || 'Error downloading files')
let fileargs = ''
if (files.length === 1) {
const result = extractSourceFromPath(decodeURI(files[0]))
fileargs = result.source + '::' + result.path + '||'
} else {
for (let file of files) {
const result = extractSourceFromPath(decodeURI(file))
fileargs += result.source + '::' + result.path + '||'
}
}
fileargs = fileargs.slice(0, -2) // remove trailing "||"
const apiPath = getApiPath('api/raw', {
files: encodeURIComponent(fileargs),
algo: format
})
const url = window.origin + apiPath
// Create a temporary <a> element to trigger the download
const link = document.createElement('a')
link.href = url
link.setAttribute('download', '') // Ensures it triggers a download
document.body.appendChild(link)
link.click()
document.body.removeChild(link) // Clean up
}
export async function post(url, content = '', overwrite = false, onupload) {
@@ -212,7 +210,8 @@ export function getDownloadURL(source, path, inline, useExternal) {
}
const apiPath = getApiPath('api/raw', params)
if (externalUrl && useExternal) {
return externalUrl + apiPath
return externalUrl + removePrefix(apiPath,baseURL)
}
return window.origin + apiPath
} catch (err) {
+23 -24
View File
@@ -1,5 +1,5 @@
import { adjustedData } from "./utils";
import { getApiPath, extractSourceFromPath } from "@/utils/url.js";
import { getApiPath } from "@/utils/url.js";
import { notify } from "@/notify";
// Fetch public share data
@@ -23,31 +23,30 @@ export async function fetchPub(path, hash, password = "") {
}
// Download files with given parameters
export function download(share, ...files) {
try {
let fileargs = ''
if (files.length === 1) {
const result = extractSourceFromPath(decodeURI(files[0]))
fileargs = result.path + '||'
} else {
for (let file of files) {
const result = extractSourceFromPath(decodeURI(file))
fileargs += result.path + '||'
}
export function download(format, files) {
console.log("Downloading files:",format, files)
let fileargs = ''
if (files.length === 1) {
fileargs = decodeURI(files[0]) + '||'
} else {
for (let file of files) {
fileargs += decodeURI(file) + '||'
}
fileargs = fileargs.slice(0, -2); // remove trailing "||"
const params = {
"files": fileargs,
"hash": share.hash,
"token": share.token,
"inline": share.inline,
};
const apiPath = getApiPath("api/public/dl", params);
window.open(window.origin+apiPath)
} catch (err) {
notify.showError(err.message || "Error downloading files");
throw err;
}
fileargs = fileargs.slice(0, -2) // remove trailing "||"
const apiPath = getApiPath('api/public/dl', {
files: encodeURIComponent(fileargs),
hash: format.hash,
})
const url = window.origin + apiPath
// Create a temporary <a> element to trigger the download
const link = document.createElement('a')
link.href = url
link.setAttribute('download', '') // Ensures it triggers a download
document.body.appendChild(link)
link.click()
document.body.removeChild(link) // Clean up
}
// Get the public user data
+4 -3
View File
@@ -1,7 +1,7 @@
import { fetchURL, fetchJSON, adjustedData } from "./utils";
import { notify } from "@/notify";
import { getApiPath } from "@/utils/url.js";
import { externalUrl } from "@/utils/constants";
import { getApiPath,removePrefix } from "@/utils/url.js";
import { externalUrl,baseURL } from "@/utils/constants";
export async function list() {
const apiPath = getApiPath("api/shares");
@@ -43,7 +43,8 @@ export async function create(path, source, password = "", expires = "", unit = "
export function getShareURL(share) {
if (externalUrl) {
return externalUrl+getApiPath(`share/${share.hash}`);
const apiPath = getApiPath(`share/${share.hash}`)
return externalUrl + removePrefix(apiPath, baseURL);
}
return window.origin+getApiPath(`share/${share.hash}`);
}
@@ -196,6 +196,7 @@ export default {
methods: {
downloadFile(event) {
event.preventDefault();
event.stopPropagation();
mutations.resetSelected();
mutations.addSelected(this.index);
downloadFiles();
+8 -2
View File
@@ -63,7 +63,7 @@
>
<i class="material-icons source-icon">folder</i>
<span>{{ name }}</span>
<div>
<div v-if="!disableUsedPercentage">
<progress-bar
:val="info.usedPercentage"
text-position="inside"
@@ -83,7 +83,13 @@
<script>
import * as auth from "@/utils/auth";
import { signup, disableExternal, noAuth, loginPage } from "@/utils/constants";
import {
signup,
disableExternal,
noAuth,
loginPage,
disableUsedPercentage,
} from "@/utils/constants";
import ProgressBar from "@/components/ProgressBar.vue";
import { state, getters, mutations } from "@/store"; // Import your custom store
+1 -1
View File
@@ -139,7 +139,7 @@ router.beforeResolve(async (to, from, next) => {
// Handle auth requirements
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (state != null && state.user != null && !state.user.username){
if (state != null && state.user != null && !state.user.username) {
await validateLogin();
}
+2 -2
View File
@@ -49,7 +49,7 @@ export default function download() {
});
}
async function startDownload(config,files, isPublic) {
async function startDownload(config, files, isPublic) {
try {
if (isPublic) {
publicApi.download(config, files);
@@ -58,6 +58,6 @@ async function startDownload(config,files, isPublic) {
}
notify.showSuccess("Downloading...");
} catch (e) {
notify.showError("Error downloading", e);
notify.showError(`Error downloading: ${e}`);
}
}
+2
View File
@@ -5,6 +5,7 @@ describe('testurl', () => {
it('url prefix', () => {
let tests = [
{input: "/files/share/hash", trimArg:"/files/",expected: "/share/hash",},
{input: "/files/files", trimArg: "/files/",expected: "/files",},
{input: "/files/share/something/", trimArg: "files", expected:"/share/something/"},
{input: "test/iscool/", trimArg: "test",expected:"/iscool/"},
@@ -25,6 +26,7 @@ describe('getapipath', () => {
it('url prefix', () => {
let tests = [
{input: "/share/to/thing", expected: "/files/share/to/thing",},
{input: "share/hash", expected: "/files/share/hash",},
]
for (let test of tests) {
expect(getApiPath(test.input)).toEqual(test.expected);
+1 -1
View File
@@ -326,7 +326,7 @@ export default {
token: this.token,
format: format,
};
publicApi.download(share, ...files);
publicApi.download(share, files);
},
});
},
@@ -33,6 +33,21 @@ test("root share path is valid", async ({ page, checkForErrors, context }) => {
checkForErrors();
});
test("share file works", async ({ page, checkForErrors, context }) => {
await page.goto("/files/");
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
const shareHashFile = await page.evaluate(() => localStorage.getItem('shareHashFile'));
if (shareHashFile == "") {
throw new Error("Share hash not found in localStorage");
}
await page.goto("/share/" + shareHashFile);
await expect(page).toHaveTitle("Graham's Filebrowser - Share - 1file1.txt");
await expect(page.locator('a[aria-label="Home"]')).toHaveAttribute("href", `/share/${shareHashFile}/`);
checkForErrors();
});
test("share download single file", async ({ page, checkForErrors, context }) => {
await page.goto("/files/");
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
+21 -1
View File
@@ -18,7 +18,7 @@ async function globalSetup() {
await page.waitForURL("**/files/playwright-files", { timeout: 250 });
// Create a share
// Create a share of folder
await page.locator('a[aria-label="myfolder"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="myfolder"]').click({ button: "right" });
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
@@ -36,6 +36,26 @@ async function globalSetup() {
localStorage.setItem('shareHash', hash);
}, shareHash);
await page.goto("http://127.0.0.1/files/playwright-files", { timeout: 250 });
// Create a share of file
await page.locator('a[aria-label="1file1.txt"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="1file1.txt"]').click({ button: "right" });
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
await page.locator('button[aria-label="Share"]').click();
await expect(page.locator('div[aria-label="share-path"]')).toHaveText('Path: /1file1.txt');
await page.locator('button[aria-label="Share-Confirm"]').click();
await expect(page.locator("#share .card-content table tbody tr:not(:has(th))")).toHaveCount(1);
const shareHashFile = await page.locator("#share .card-content table tbody tr:not(:has(th)) td").first().textContent();
if (!shareHashFile) {
throw new Error("Failed to retrieve shareHash");
}
// Store shareHash in localStorage
await page.evaluate((hash) => {
localStorage.setItem('shareHashFile', hash);
}, shareHashFile);
await context.storageState({ path: "./loginAuth.json" });
await browser.close();
}
+1
View File
@@ -1,3 +1,4 @@
SHELL := /bin/bash
.SILENT:
setup:
echo "creating ./backend/test_config.yaml for local testing..." && \