diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f6b22f07..e5c3165f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '16' - run: make lint-frontend lint-backend: runs-on: ubuntu-latest @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18.3 - run: make lint-backend lint-commits: runs-on: ubuntu-latest @@ -34,7 +34,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '16' - run: make lint-commits lint: runs-on: ubuntu-latest @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '16' - run: make test-frontend test-backend: runs-on: ubuntu-latest @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18.3 - run: make test-backend test: runs-on: ubuntu-latest @@ -76,10 +76,10 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18.3 - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '16' - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx diff --git a/.golangci.yml b/.golangci.yml index 54ecb4a0..c946c73a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -109,6 +109,7 @@ issues: - gomnd run: + go: '1.18' skip-dirs: - frontend/ skip-files: diff --git a/CHANGELOG.md b/CHANGELOG.md index 54faa62d..54924897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,53 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [2.22.1](https://github.com/filebrowser/filebrowser/compare/v2.22.0...v2.22.1) (2022-06-06) + + +### Bug Fixes + +* use correct basepath prefix for preview urls ([#1971](https://github.com/filebrowser/filebrowser/issues/1971)) ([1e7d3b2](https://github.com/filebrowser/filebrowser/commit/1e7d3b25c283c556d98c65f1c2f46db4e4178995)) + + +### Build + +* **backend:** bump go version to 1.8.3 ([b16982d](https://github.com/filebrowser/filebrowser/commit/b16982df0f7da9eedb678455298b42ac55c86666)) + +## [2.22.0](https://github.com/filebrowser/filebrowser/compare/v2.21.1...v2.22.0) (2022-06-03) + + +### Features + +* add branding to the window title ([#1850](https://github.com/filebrowser/filebrowser/issues/1850)) ([f8dfbf7](https://github.com/filebrowser/filebrowser/commit/f8dfbf7eeecf3ee99ce906276777676f44e81e34)) +* add disk usage information to the sidebar ([d1d8e3e](https://github.com/filebrowser/filebrowser/commit/d1d8e3e3405381b01317fe07ae729d70219415a7)) +* automatically focus username field on login page ([596c732](https://github.com/filebrowser/filebrowser/commit/596c73288f5b53bd7e79ab8046136dc75ff078b9)) +* invalid symlink icon ([b14b911](https://github.com/filebrowser/filebrowser/commit/b14b9114f837cacf9f7788e88c503142a81585be)) +* page title localization ([8a43413](https://github.com/filebrowser/filebrowser/commit/8a43413f888440dc11b11c509abff45f706033d8)) + + +### Bug Fixes + +* allow CSP inline styling ([5da9d74](https://github.com/filebrowser/filebrowser/commit/5da9d74da62c69c431361bcaf0c07dc1da237ea8)) +* disable autocapitalize of login input (closes [#1910](https://github.com/filebrowser/filebrowser/issues/1910)) ([aed3af5](https://github.com/filebrowser/filebrowser/commit/aed3af58384697dc3de30f1450b837b0b74e4fa6)) +* drag-and-drop folder upload ([e677c78](https://github.com/filebrowser/filebrowser/commit/e677c78471f09f8d2c21d63d7388e908924aa6d9)) +* expired token error ([c3bd118](https://github.com/filebrowser/filebrowser/commit/c3bd1188aa396cbf00c593d259a9da0eddeeea3b)) +* folder info on upload list ([d1d7b23](https://github.com/filebrowser/filebrowser/commit/d1d7b23da6cc0c9a2f2f3e17021ec4f13ea557dd)) +* network error object message ([fc209f6](https://github.com/filebrowser/filebrowser/commit/fc209f64deff7a2793980d11ee738f7140c444cf)) +* set correct scope when user home creation is enabled ([02730bb](https://github.com/filebrowser/filebrowser/commit/02730bb9bfa3bfbfa251bb4736fc4c08d33609ab)) + + +### Build + +* **backend:** bump dependency versions ([7c9a75e](https://github.com/filebrowser/filebrowser/commit/7c9a75e72588f92d58fb58d32cdac352bce73b20)) +* **deps:** bump async from 2.6.3 to 2.6.4 in /frontend ([#1933](https://github.com/filebrowser/filebrowser/issues/1933)) ([e5fa96b](https://github.com/filebrowser/filebrowser/commit/e5fa96b666eac2e46a02bde832488baca5f2cd6d)) +* **deps:** bump eventsource from 1.1.0 to 1.1.1 in /frontend ([dd50369](https://github.com/filebrowser/filebrowser/commit/dd503695a1a8119a631643414d3a9070890f3f3c)) +* **deps:** bump minimist from 1.2.5 to 1.2.6 in /frontend ([#1889](https://github.com/filebrowser/filebrowser/issues/1889)) ([a74c72d](https://github.com/filebrowser/filebrowser/commit/a74c72db451207e1275988f3d208fa6d6f0468a9)) +* **deps:** bump minimist from 1.2.5 to 1.2.6 in /tools ([#1891](https://github.com/filebrowser/filebrowser/issues/1891)) ([f5b1e10](https://github.com/filebrowser/filebrowser/commit/f5b1e106183fb2192063a72fd195fc8c181ba8f9)) +* **deps:** bump moment from 2.29.1 to 2.29.2 in /frontend ([#1900](https://github.com/filebrowser/filebrowser/issues/1900)) ([040584c](https://github.com/filebrowser/filebrowser/commit/040584c86563d869c7a05887ef1f781bce653033)) +* **deps:** bump url-parse from 1.5.7 to 1.5.10 in /frontend ([#1841](https://github.com/filebrowser/filebrowser/issues/1841)) ([b2ad3f7](https://github.com/filebrowser/filebrowser/commit/b2ad3f73686a2abaa4fc62963fba6f83c9da9b5e)) +* **frontend:** bump node version from 14 to 16 ([ac3ead8](https://github.com/filebrowser/filebrowser/commit/ac3ead8dcef9c64c6be8b5cbbceee143b2cc77a8)) +* upgrade go version to 1.18.1 ([6bd34c7](https://github.com/filebrowser/filebrowser/commit/6bd34c76324780c1edd8625d5b22f5a84990852b)) + ### [2.21.1](https://github.com/filebrowser/filebrowser/compare/v2.21.0...v2.21.1) (2022-02-22) diff --git a/cmd/root.go b/cmd/root.go index 1beace66..5b314b61 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -312,9 +312,10 @@ func setupLog(logMethod string) { func quickSetup(flags *pflag.FlagSet, d pythonData) { set := &settings.Settings{ - Key: generateKey(), - Signup: false, - CreateUserDir: false, + Key: generateKey(), + Signup: false, + CreateUserDir: false, + UserHomeBasePath: settings.DefaultUsersHomeBasePath, Defaults: settings.UserDefaults{ Scope: ".", Locale: "en", @@ -330,6 +331,11 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { Download: true, }, }, + AuthMethod: "", + Branding: settings.Branding{}, + Commands: nil, + Shell: nil, + Rules: nil, } var err error diff --git a/cmd/utils.go b/cmd/utils.go index 57d1e137..2bd9e760 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - "github.com/asdine/storm" + "github.com/asdine/storm/v3" "github.com/spf13/cobra" "github.com/spf13/pflag" yaml "gopkg.in/yaml.v2" diff --git a/files/file.go b/files/file.go index 569b0be4..2d96d4c5 100644 --- a/files/file.go +++ b/files/file.go @@ -322,7 +322,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error { continue } - isSymlink := false + isSymlink, isInvalidLink := false, false if IsSymlink(f.Mode()) { isSymlink = true // It's a symbolic link. We try to follow it. If it doesn't work, @@ -330,6 +330,8 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error { info, err := i.Fs.Stat(fPath) if err == nil { f = info + } else { + isInvalidLink = true } } @@ -350,9 +352,13 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error { } else { listing.NumFiles++ - err := file.detectType(true, false, readHeader) - if err != nil { - return err + if isInvalidLink { + file.Type = "invalid_link" + } else { + err := file.detectType(true, false, readHeader) + if err != nil { + return err + } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e5362482..ecf26329 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,15 +16,18 @@ "lodash.clonedeep": "^4.5.0", "lodash.throttle": "^4.1.1", "material-icons": "^1.10.5", - "moment": "^2.24.0", + "moment": "^2.29.2", "normalize.css": "^8.0.1", "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" @@ -2745,9 +2748,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -6148,9 +6151,9 @@ } }, "node_modules/eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz", + "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==", "dev": true, "dependencies": { "original": "^1.0.0" @@ -9328,9 +9331,9 @@ } }, "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", "engines": { "node": "*" } @@ -11127,6 +11130,17 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-bytes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz", + "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-error": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", @@ -13839,6 +13853,14 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" }, + "node_modules/vue-async-computed": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/vue-async-computed/-/vue-async-computed-3.9.0.tgz", + "integrity": "sha512-ac6m/9zxHHNGGKNOU1en8qNk+fAmEbJLuWL7qyQTFuH3vjv3V4urv//QHcVzCobROM6btnaDG2b+XYMncF/ETA==", + "peerDependencies": { + "vue": "~2" + } + }, "node_modules/vue-eslint-parser": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.6.0.tgz", @@ -14018,6 +14040,11 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz", "integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw==" }, + "node_modules/vue-simple-progress": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vue-simple-progress/-/vue-simple-progress-1.1.1.tgz", + "integrity": "sha512-ltLWYBA5eVQHWyt1NwZeGeK0VQC69JVh1oqUdro0po7r8Hc8SEMEyEfuwyCO4s27h5I3jbD99BKKkyPSQZgoZA==" + }, "node_modules/vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -17646,9 +17673,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -20379,9 +20406,9 @@ "dev": true }, "eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz", + "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==", "dev": true, "requires": { "original": "^1.0.0" @@ -22897,9 +22924,9 @@ } }, "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" }, "move-concurrently": { "version": "1.0.1", @@ -24394,6 +24421,11 @@ "fast-diff": "^1.1.2" } }, + "pretty-bytes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz", + "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==" + }, "pretty-error": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", @@ -26664,6 +26696,12 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" }, + "vue-async-computed": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/vue-async-computed/-/vue-async-computed-3.9.0.tgz", + "integrity": "sha512-ac6m/9zxHHNGGKNOU1en8qNk+fAmEbJLuWL7qyQTFuH3vjv3V4urv//QHcVzCobROM6btnaDG2b+XYMncF/ETA==", + "requires": {} + }, "vue-eslint-parser": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.6.0.tgz", @@ -26801,6 +26839,11 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz", "integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw==" }, + "vue-simple-progress": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vue-simple-progress/-/vue-simple-progress-1.1.1.tgz", + "integrity": "sha512-ltLWYBA5eVQHWyt1NwZeGeK0VQC69JVh1oqUdro0po7r8Hc8SEMEyEfuwyCO4s27h5I3jbD99BKKkyPSQZgoZA==" + }, "vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index d07ef304..f2a441d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,15 +18,18 @@ "lodash.clonedeep": "^4.5.0", "lodash.throttle": "^4.1.1", "material-icons": "^1.10.5", - "moment": "^2.24.0", + "moment": "^2.29.2", "normalize.css": "^8.0.1", "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" diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index 7494e55b..a91ba4b2 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -1,4 +1,4 @@ -import { fetchURL, removePrefix } from "./utils"; +import { createURL, fetchURL, removePrefix } from "./utils"; import { baseURL } from "@/utils/constants"; import store from "@/store"; @@ -7,28 +7,24 @@ export async function fetch(url) { const res = await fetchURL(`/api/resources${url}`, {}); - if (res.status === 200) { - let data = await res.json(); - data.url = `/files${url}`; + let data = await res.json(); + data.url = `/files${url}`; - if (data.isDir) { - if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; + if (data.isDir) { + if (!data.url.endsWith("/")) data.url += "/"; + data.items = data.items.map((item, index) => { + item.index = index; + item.url = `${data.url}${encodeURIComponent(item.name)}`; - if (item.isDir) { - item.url += "/"; - } + if (item.isDir) { + item.url += "/"; + } - return item; - }); - } - - return data; - } else { - throw new Error(res.status); + return item; + }); } + + return data; } async function resourceAction(url, method, content) { @@ -42,11 +38,7 @@ async function resourceAction(url, method, content) { const res = await fetchURL(`/api/resources${url}`, opts); - if (res.status !== 200) { - throw new Error(await res.text()); - } else { - return res; - } + return res; } export async function remove(url) { @@ -119,8 +111,8 @@ export async function post(url, content = "", overwrite = false, onupload) { } }; - request.onerror = (error) => { - reject(error); + request.onerror = () => { + reject(new Error("001 Connection aborted")); }; request.send(bufferContent || content); @@ -154,3 +146,41 @@ export async function checksum(url, algo) { const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); return (await data.json()).checksums[algo]; } + +export function getDownloadURL(file, inline) { + const params = { + ...(inline && { inline: "true" }), + }; + + return createURL("api/raw" + file.path, params); +} + +export function getPreviewURL(file, size) { + const params = { + inline: "true", + key: Date.parse(file.modified), + }; + + return createURL("api/preview/" + size + file.path, params); +} + +export function getSubtitlesURL(file) { + const params = { + inline: "true", + }; + + const subtitles = []; + for (const sub of file.subtitles) { + subtitles.push(createURL("api/raw" + sub, params)); + } + + return subtitles; +} + +export async function usage(url) { + url = removePrefix(url); + + const res = await fetchURL(`/api/usage${url}`, {}); + + return await res.json(); +} diff --git a/frontend/src/api/pub.js b/frontend/src/api/pub.js index 58eb1eb6..1511143d 100644 --- a/frontend/src/api/pub.js +++ b/frontend/src/api/pub.js @@ -1,35 +1,35 @@ -import { fetchURL, removePrefix } from "./utils"; +import { fetchURL, removePrefix, createURL } from "./utils"; import { baseURL } from "@/utils/constants"; export async function fetch(url, password = "") { url = removePrefix(url); - const res = await fetchURL(`/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) { - let data = await res.json(); - data.url = `/share${url}`; + 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) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; + if (data.isDir) { + if (!data.url.endsWith("/")) data.url += "/"; + data.items = data.items.map((item, index) => { + item.index = index; + item.url = `${data.url}${encodeURIComponent(item.name)}`; - if (item.isDir) { - item.url += "/"; - } + if (item.isDir) { + item.url += "/"; + } - return item; - }); - } - - return data; - } else { - throw new Error(res.status); + return item; + }); } + + return data; } export function download(format, hash, token, ...files) { @@ -59,3 +59,12 @@ export function download(format, hash, token, ...files) { window.open(url); } + +export function getDownloadURL(share, inline = false) { + const params = { + ...(inline && { inline: "true" }), + ...(share.token && { token: share.token }), + }; + + return createURL("api/public/dl/" + share.hash + share.path, params, false); +} diff --git a/frontend/src/api/search.js b/frontend/src/api/search.js index 08be5c1d..42846880 100644 --- a/frontend/src/api/search.js +++ b/frontend/src/api/search.js @@ -11,21 +11,17 @@ export default async function search(base, query) { let res = await fetchURL(`/api/search${base}?query=${query}`, {}); - if (res.status === 200) { - let data = await res.json(); + let data = await res.json(); - data = data.map((item) => { - item.url = `/files${base}` + url.encodePath(item.path); + data = data.map((item) => { + item.url = `/files${base}` + url.encodePath(item.path); - if (item.dir) { - item.url += "/"; - } + if (item.dir) { + item.url += "/"; + } - return item; - }); + return item; + }); - return data; - } else { - throw Error(res.status); - } + return data; } diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.js index 8abe1f1e..e03b0db1 100644 --- a/frontend/src/api/settings.js +++ b/frontend/src/api/settings.js @@ -5,12 +5,8 @@ export function get() { } export async function update(settings) { - const res = await fetchURL(`/api/settings`, { + await fetchURL(`/api/settings`, { method: "PUT", body: JSON.stringify(settings), }); - - if (res.status !== 200) { - throw new Error(res.status); - } } diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js index 54bbc460..1ac4473a 100644 --- a/frontend/src/api/share.js +++ b/frontend/src/api/share.js @@ -1,4 +1,4 @@ -import { fetchURL, fetchJSON, removePrefix } from "./utils"; +import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; export async function list() { return fetchJSON("/api/shares"); @@ -10,13 +10,9 @@ export async function get(url) { } export async function remove(hash) { - const res = await fetchURL(`/api/share/${hash}`, { + await fetchURL(`/api/share/${hash}`, { method: "DELETE", }); - - if (res.status !== 200) { - throw new Error(res.status); - } } export async function create(url, password = "", expires = "", unit = "hours") { @@ -34,3 +30,7 @@ export async function create(url, password = "", expires = "", unit = "hours") { body: body, }); } + +export function getShareURL(share) { + return createURL("share/" + share.hash, {}, false); +} diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js index 7975d66a..105d6cc0 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.js @@ -20,13 +20,11 @@ export async function create(user) { if (res.status === 201) { return res.headers.get("Location"); - } else { - throw new Error(res.status); } } export async function update(user, which = ["all"]) { - const res = await fetchURL(`/api/users/${user.id}`, { + await fetchURL(`/api/users/${user.id}`, { method: "PUT", body: JSON.stringify({ what: "user", @@ -34,18 +32,10 @@ export async function update(user, which = ["all"]) { data: user, }), }); - - if (res.status !== 200) { - throw new Error(res.status); - } } export async function remove(id) { - const res = await fetchURL(`/api/users/${id}`, { + await fetchURL(`/api/users/${id}`, { method: "DELETE", }); - - if (res.status !== 200) { - throw new Error(res.status); - } } diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js index 65c6740a..87d6a243 100644 --- a/frontend/src/api/utils.js +++ b/frontend/src/api/utils.js @@ -1,8 +1,9 @@ import store from "@/store"; -import { renew } from "@/utils/auth"; +import { renew, logout } from "@/utils/auth"; import { baseURL } from "@/utils/constants"; +import { encodePath } from "@/utils/url"; -export async function fetchURL(url, opts) { +export async function fetchURL(url, opts, auth = true) { opts = opts || {}; opts.headers = opts.headers || {}; @@ -17,14 +18,28 @@ export async function fetchURL(url, opts) { }, ...rest, }); - } catch (error) { - return { status: 0 }; + } catch { + const error = new Error("000 No connection"); + error.status = 0; + + throw error; } - if (res.headers.get("X-Renew-Token") === "true") { + if (auth && res.headers.get("X-Renew-Token") === "true") { await renew(store.state.jwt); } + if (res.status < 200 || res.status > 299) { + const error = new Error(await res.text()); + error.status = res.status; + + if (auth && res.status == 401) { + logout(); + } + + throw error; + } + return res; } @@ -45,3 +60,22 @@ export function removePrefix(url) { if (url[0] !== "/") url = "/" + url; return url; } + +export function createURL(endpoint, params = {}, auth = true) { + let prefix = baseURL; + if (!prefix.endsWith("/")) { + prefix = prefix + "/"; + } + const url = new URL(prefix + encodePath(endpoint), origin); + + const searchParams = { + ...(auth && { auth: store.state.jwt }), + ...params, + }; + + for (const key in searchParams) { + url.searchParams.set(key, searchParams[key]); + } + + return url.toString(); +} diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index 2986b6c7..3db65648 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -80,6 +80,16 @@ +
+ +
+ {{ usage.used }} of {{ usage.total }} used +
+

File Browser @@ -92,9 +102,9 @@ > {{ version }} - {{ $t("sidebar.help") }} + + {{ $t("sidebar.help") }} +

@@ -109,9 +119,15 @@ import { noAuth, authMethod, } from "@/utils/constants"; +import { files as api } from "@/api"; +import ProgressBar from "vue-simple-progress"; +import prettyBytes from "pretty-bytes"; export default { name: "sidebar", + components: { + ProgressBar, + }, computed: { ...mapState(["user"]), ...mapGetters(["isLogged"]), @@ -124,6 +140,31 @@ export default { noAuth: () => noAuth, authMethod: () => authMethod, }, + asyncComputed: { + usage: { + async get() { + let path = this.$route.path.endsWith("/") + ? this.$route.path + : this.$route.path + "/"; + let usageStats = { used: 0, total: 0, usedPercentage: 0 }; + try { + let usage = await api.usage(path); + usageStats = { + used: prettyBytes(usage.used, { binary: true }), + total: prettyBytes(usage.total, { 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: { toRoot() { this.$router.push({ path: "/files/" }, () => {}); diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue index 351fba1f..b2853fcb 100644 --- a/frontend/src/components/files/ListingItem.vue +++ b/frontend/src/components/files/ListingItem.vue @@ -35,7 +35,7 @@ diff --git a/frontend/src/css/listing-icons.css b/frontend/src/css/listing-icons.css index 2de6baab..32c87755 100644 --- a/frontend/src/css/listing-icons.css +++ b/frontend/src/css/listing-icons.css @@ -11,6 +11,7 @@ .file-icons [data-type=pdf] i::before { content: 'description' } .file-icons [data-type=text] i::before { content: 'description' } .file-icons [data-type=video] i::before { content: 'movie' } +.file-icons [data-type=invalid_link] i::before { content: 'link_off' } /* #f90 - Image */ @@ -125,6 +126,7 @@ .file-icons [data-type=audio] i { color: var(--icon-yellow) } .file-icons [data-type=image] i { color: var(--icon-orange) } .file-icons [data-type=video] i { color: var(--icon-violet) } +.file-icons [data-type=invalid_link] i { color: var(--icon-red) } /* #f00 - Adobe/Oracle */ diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 182c6c08..6aaa6145 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -182,6 +182,9 @@ "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandsUpdated": "Commands updated!", "createUserDir": "Auto create user home dir while adding new user", + "userHomeBasePath": "Base path for user home directories", + "userScopeGenerationPlaceholder": "The scope will be auto generated", + "createUserHomeDirectory": "Create user home directory", "customStylesheet": "Custom Stylesheet", "defaultUserDescription": "This are the default settings for new users.", "disableExternalLinks": "Disable external links (except documentation)", diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 27e2e711..6af06f33 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -13,9 +13,25 @@ import Shares from "@/views/settings/Shares"; import Errors from "@/views/Errors"; import store from "@/store"; import { baseURL, name } from "@/utils/constants"; +import i18n from "@/i18n"; Vue.use(Router); +const titles = { + Login: "sidebar.login", + Share: "buttons.share", + Files: "files.files", + Settings: "sidebar.settings", + ProfileSettings: "settings.profileSettings", + Shares: "settings.shareManagement", + GlobalSettings: "settings.globalSettings", + Users: "settings.users", + User: "settings.user", + Forbidden: "errors.forbidden", + NotFound: "errors.notFound", + InternalServerError: "errors.internal", +}; + const router = new Router({ base: baseURL, mode: "history", @@ -29,7 +45,6 @@ const router = new Router({ return next({ path: "/files" }); } - document.title = "Login - " + name; next(); }, }, @@ -63,7 +78,7 @@ const router = new Router({ children: [ { path: "/settings/profile", - name: "Profile Settings", + name: "ProfileSettings", component: ProfileSettings, }, { @@ -73,7 +88,7 @@ const router = new Router({ }, { path: "/settings/global", - name: "Global Settings", + name: "GlobalSettings", component: GlobalSettings, meta: { requiresAdmin: true, @@ -108,7 +123,7 @@ const router = new Router({ }, { path: "/404", - name: "Not Found", + name: "NotFound", component: Errors, props: { errorCode: 404, @@ -117,7 +132,7 @@ const router = new Router({ }, { path: "/500", - name: "Internal Server Error", + name: "InternalServerError", component: Errors, props: { errorCode: 500, @@ -140,7 +155,8 @@ const router = new Router({ }); router.beforeEach((to, from, next) => { - document.title = to.name + " - " + name; + const title = i18n.t(titles[to.name]); + document.title = title + " - " + name; if (to.matched.some((record) => record.meta.requiresAuth)) { if (!store.getters.isLogged) { diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js index eee94fe2..6bee9bcd 100644 --- a/frontend/src/store/getters.js +++ b/frontend/src/store/getters.js @@ -25,15 +25,19 @@ const getters = { let upload = state.upload.uploads[index]; let id = upload.id; let type = upload.type; - let name = decodeURIComponent(upload.path.replace(/^.*[\\/]/, "")); - let progress = state.upload.progress[id]; + let name = upload.file.name; let size = state.upload.sizes[id]; + let isDir = upload.file.isDir; + let progress = isDir + ? 100 + : Math.ceil((state.upload.progress[id] / size) * 100); files.push({ id, name, - progress: Math.ceil((progress / size) * 100), + progress, type, + isDir, }); } diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 9761339b..200c4e8d 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -14,6 +14,7 @@ const theme = window.FileBrowser.Theme; const enableThumbs = window.FileBrowser.EnableThumbs; const resizePreview = window.FileBrowser.ResizePreview; const enableExec = window.FileBrowser.EnableExec; +const origin = window.location.origin; export { name, @@ -31,4 +32,5 @@ export { enableThumbs, resizePreview, enableExec, + origin, }; diff --git a/frontend/src/utils/upload.js b/frontend/src/utils/upload.js index e1222024..2184072f 100644 --- a/frontend/src/utils/upload.js +++ b/frontend/src/utils/upload.js @@ -70,6 +70,7 @@ export function scanFiles(dt) { isDir: true, size: 0, fullPath: `${directory}${entry.name}`, + name: entry.name, }; contents.push(dir); @@ -129,7 +130,7 @@ export function handleFiles(files, base, overwrite = false) { path, file, overwrite, - type: detectType(file.type), + ...(!file.isDir && { type: detectType(file.type) }), }; store.dispatch("upload/upload", item); diff --git a/frontend/src/utils/url.js b/frontend/src/utils/url.js index 8346bb03..33d124a2 100644 --- a/frontend/src/utils/url.js +++ b/frontend/src/utils/url.js @@ -1,4 +1,4 @@ -function removeLastDir(url) { +export function removeLastDir(url) { var arr = url.split("/"); if (arr.pop() === "") { arr.pop(); @@ -9,7 +9,7 @@ function removeLastDir(url) { // this code borrow from mozilla // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples -function encodeRFC5987ValueChars(str) { +export function encodeRFC5987ValueChars(str) { return ( encodeURIComponent(str) // Note that although RFC3986 reserves "!", RFC5987 does not, @@ -22,7 +22,7 @@ function encodeRFC5987ValueChars(str) { ); } -function encodePath(str) { +export function encodePath(str) { return str .split("/") .map((v) => encodeURIComponent(v)) @@ -30,7 +30,7 @@ function encodePath(str) { } export default { - encodeRFC5987ValueChars: encodeRFC5987ValueChars, - removeLastDir: removeLastDir, - encodePath: encodePath, + encodeRFC5987ValueChars, + removeLastDir, + encodePath, }; diff --git a/frontend/src/utils/vue.js b/frontend/src/utils/vue.js index 74df7acc..962a7f9d 100644 --- a/frontend/src/utils/vue.js +++ b/frontend/src/utils/vue.js @@ -3,8 +3,10 @@ import Noty from "noty"; import VueLazyload from "vue-lazyload"; import i18n from "@/i18n"; import { disableExternal } from "@/utils/constants"; +import AsyncComputed from "vue-async-computed"; Vue.use(VueLazyload); +Vue.use(AsyncComputed); Vue.config.productionTip = true; diff --git a/frontend/src/views/Errors.vue b/frontend/src/views/Errors.vue index b010906d..43746105 100644 --- a/frontend/src/views/Errors.vue +++ b/frontend/src/views/Errors.vue @@ -38,15 +38,8 @@ export default { }, props: ["errorCode", "showHeader"], computed: { - code() { - return this.errorCode === "0" || - this.errorCode === "404" || - this.errorCode === "403" - ? parseInt(this.errorCode) - : 500; - }, info() { - return errors[this.code]; + return errors[this.errorCode] ? errors[this.errorCode] : errors[500]; }, }, }; diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue index 128b8a9b..9a20f39c 100644 --- a/frontend/src/views/Files.vue +++ b/frontend/src/views/Files.vue @@ -4,7 +4,7 @@ - +

@@ -28,7 +28,6 @@ import Breadcrumbs from "@/components/Breadcrumbs"; import Errors from "@/views/Errors"; import Preview from "@/views/files/Preview"; import Listing from "@/views/files/Listing"; -import { name } from "@/utils/constants"; function clean(path) { return path.endsWith("/") ? path.slice(0, -1) : path; @@ -52,7 +51,6 @@ export default { }, computed: { ...mapState(["req", "reload", "loading", "show"]), - name: () => name, currentView() { if (this.req.type == undefined) { return null; @@ -118,7 +116,7 @@ export default { } this.$store.commit("updateRequest", res); - document.title = `${res.name} - ${this.$route.name} - ${this.name}`; + document.title = `${res.name} - ${document.title}`; } catch (e) { this.error = e; } finally { diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 50d4392d..27bb5847 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -8,6 +8,8 @@ @@ -69,6 +71,8 @@ export default { }; }, mounted() { + this.focusUsername(); + if (!recaptcha) return; window.grecaptcha.ready(function () { @@ -78,6 +82,9 @@ export default { }); }, methods: { + focusUsername() { + this.$refs.username.focus(); + }, toggleMode() { this.createMode = !this.createMode; }, diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index eefc6453..5e9feb2d 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -30,7 +30,7 @@

-
+
- +
import { mapState, mapMutations, mapGetters } from "vuex"; import { pub as api } from "@/api"; -import { baseURL } from "@/utils/constants"; import filesize from "filesize"; import moment from "moment"; @@ -231,21 +230,10 @@ export default { return "insert_drive_file"; }, link: function () { - let queryArg = ""; - if (this.token !== "") { - queryArg = `?token=${this.token}`; - } - - const path = this.$route.path.split("/").splice(2).join("/"); - return `${baseURL}/api/public/dl/${path}${queryArg}`; + return api.getDownloadURL(this.req); }, inlineLink: function () { - let url = new URL(this.fullLink); - url.searchParams.set("inline", "true"); - return url.href; - }, - fullLink: function () { - return window.location.origin + this.link; + return api.getDownloadURL(this.req, true); }, humanSize: function () { if (this.req.isDir) { @@ -287,11 +275,12 @@ export default { try { let file = await api.fetch(url, this.password); + file.hash = this.hash; this.token = file.token || ""; this.updateRequest(file); - document.title = `${file.name} - ${this.$route.name}`; + document.title = `${file.name} - ${document.title}`; } catch (e) { this.error = e; } finally { diff --git a/frontend/src/views/files/Listing.vue b/frontend/src/views/files/Listing.vue index 4286e8dc..bdf4806b 100644 --- a/frontend/src/views/files/Listing.vue +++ b/frontend/src/views/files/Listing.vue @@ -209,6 +209,7 @@ v-bind:modified="item.modified" v-bind:type="item.type" v-bind:size="item.size" + v-bind:path="item.path" >
@@ -225,6 +226,7 @@ v-bind:modified="item.modified" v-bind:type="item.type" v-bind:size="item.size" + v-bind:path="item.path" >
diff --git a/frontend/src/views/files/Preview.vue b/frontend/src/views/files/Preview.vue index 4e699dca..d08ac5aa 100644 --- a/frontend/src/views/files/Preview.vue +++ b/frontend/src/views/files/Preview.vue @@ -103,7 +103,7 @@ @@ -145,7 +145,7 @@