Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1f658633d | ||
|
|
9c79105c02 | ||
|
|
6d5ceae8b4 | ||
|
|
381f09087a | ||
|
|
426b38bb33 | ||
|
|
488d98045e | ||
|
|
7955e0720b | ||
|
|
e017a19985 | ||
|
|
f8df76f526 | ||
|
|
11ebaec5f0 | ||
|
|
326b35a7ac | ||
|
|
5bf15548d0 | ||
|
|
6a734c0139 | ||
|
|
81b6f4d6f6 | ||
|
|
0b92d94570 | ||
|
|
fc5506179a | ||
|
|
0fe34ad224 | ||
|
|
54f35701a2 | ||
|
|
a809404ce1 | ||
|
|
fb32e44b47 | ||
|
|
2a1f759e9e | ||
|
|
edb9e85efd | ||
|
|
d8306559fd | ||
|
|
7b6579ac8a | ||
|
|
057307181e | ||
|
|
4fb832c042 | ||
|
|
e503cb69f2 | ||
|
|
95811e99bc | ||
|
|
62fff5ca60 | ||
|
|
5b28aa0848 | ||
|
|
db5aad8eb6 | ||
|
|
1819377897 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,11 +5,9 @@ _old
|
||||
rice-box.go
|
||||
.idea/
|
||||
filebrowser
|
||||
dist/
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
/frontend/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
|
||||
@@ -3,10 +3,6 @@ project_name: filebrowser
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- make bundle-frontend
|
||||
|
||||
build:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
|
||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -2,6 +2,38 @@
|
||||
|
||||
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.13.0](https://github.com/filebrowser/filebrowser/compare/v2.12.1...v2.13.0) (2021-03-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* dual pane settings view ([db5aad8](https://github.com/filebrowser/filebrowser/commit/db5aad8eb679cfe1b1ace5142cf342951217f0f7))
|
||||
* improved settings navbar ([5b28aa0](https://github.com/filebrowser/filebrowser/commit/5b28aa0848710b9d3ee02a2aa912856395f48bd2))
|
||||
* improved sharing prompt ([1819377](https://github.com/filebrowser/filebrowser/commit/18193778971e27d18b5a35df8c2d0e2953b48111))
|
||||
* increased header button counter size ([4fb832c](https://github.com/filebrowser/filebrowser/commit/4fb832c0422107e16f22b7aa928224f36de4978f))
|
||||
* larger previewer content ([62fff5c](https://github.com/filebrowser/filebrowser/commit/62fff5ca60da1f887c1f95fa4808d3753596dab2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* archive contains parent path on Windows ([54f3570](https://github.com/filebrowser/filebrowser/commit/54f35701a2bd5cb7ec0628ca9789047072c073db))
|
||||
* check rules on http resource handlers ([5bf1554](https://github.com/filebrowser/filebrowser/commit/5bf15548d0ad147acfad5000277531be2671f7ce))
|
||||
* download current dir on file listing ([488d980](https://github.com/filebrowser/filebrowser/commit/488d98045e7476ed11e53c13d9498a9db3165bbc))
|
||||
* encoded file path on share ([7955e07](https://github.com/filebrowser/filebrowser/commit/7955e0720baef3710106c7e69bbbf078d5489220))
|
||||
* full file path on share ([e017a19](https://github.com/filebrowser/filebrowser/commit/e017a199850e19dd51b960ba59402c215fd8f1af))
|
||||
* header dropdown icon color on previewer ([f8df76f](https://github.com/filebrowser/filebrowser/commit/f8df76f52684f10722ce123fec2c90e321ddf103))
|
||||
* item dragging on file listing ([326b35a](https://github.com/filebrowser/filebrowser/commit/326b35a7ac7871afcdf892ca150349665b7f6379))
|
||||
* modified time on info prompt ([11ebaec](https://github.com/filebrowser/filebrowser/commit/11ebaec5f0671ec02ebe55d4a73a514bce3a6713))
|
||||
* root path name on archive ([426b38b](https://github.com/filebrowser/filebrowser/commit/426b38bb3362d2d477d0d8aa27d880664d537431))
|
||||
* stuck icon on header button ([6a734c0](https://github.com/filebrowser/filebrowser/commit/6a734c01391b437c2842f5d97fb63f29a0017510))
|
||||
* update image cache when replacing ([81b6f4d](https://github.com/filebrowser/filebrowser/commit/81b6f4d6f6a01886583016f61f4f1951a59f244d))
|
||||
* wait for async command exit ([#1326](https://github.com/filebrowser/filebrowser/issues/1326)) ([6d5ceae](https://github.com/filebrowser/filebrowser/commit/6d5ceae8b454edd749b3b65c88aacc0a31ce9215))
|
||||
|
||||
|
||||
### Refactorings
|
||||
|
||||
* migrate from rice to embed.FS ([fc55061](https://github.com/filebrowser/filebrowser/commit/fc5506179a64e9e2f57f7b6d6cce4b95f5ebc235))
|
||||
|
||||
### [2.12.1](https://github.com/filebrowser/filebrowser/compare/v2.12.0...v2.12.1) (2021-03-07)
|
||||
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@@ -37,9 +37,6 @@ $(BIN)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lin
|
||||
GOIMPORTS = $(BIN)/goimports
|
||||
$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports@v0.1.0
|
||||
|
||||
RICE = $(BIN)/rice
|
||||
$(BIN)/rice: PACKAGE=github.com/GeertJohan/go.rice/rice@v1.0.2
|
||||
|
||||
## build: Build
|
||||
.PHONY: build
|
||||
build: | build-frontend build-backend ; $(info $(M) building…)
|
||||
@@ -51,12 +48,9 @@ build-frontend: | ; $(info $(M) building frontend…)
|
||||
|
||||
## build-backend: Build backend
|
||||
.PHONY: build-backend
|
||||
build-backend: bundle-frontend | ; $(info $(M) building backend…)
|
||||
build-backend: | ; $(info $(M) building backend…)
|
||||
$Q $(GO) build -ldflags '$(LDFLAGS)' -o filebrowser
|
||||
|
||||
bundle-frontend: | $(RICE) ; $(info $(M) building backend…)
|
||||
$Q cd ./http && rm -rf rice-box.go && $(RICE) embed-go
|
||||
|
||||
## test: Run all tests
|
||||
.PHONY: test
|
||||
test: | test-frontend test-backend ; $(info $(M) running tests…)
|
||||
|
||||
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/auth"
|
||||
"github.com/filebrowser/filebrowser/v2/diskcache"
|
||||
"github.com/filebrowser/filebrowser/v2/frontend"
|
||||
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
||||
"github.com/filebrowser/filebrowser/v2/img"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
@@ -168,7 +170,12 @@ user created with the credentials from options "username" and "password".`,
|
||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||
go cleanupHandler(listener, sigc)
|
||||
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server)
|
||||
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
||||
checkErr(err)
|
||||
|
||||
defer listener.Close()
|
||||
|
||||
10
frontend/assets.go
Normal file
10
frontend/assets.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package frontend
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed dist/*
|
||||
var assets embed.FS
|
||||
|
||||
func Assets() embed.FS {
|
||||
return assets
|
||||
}
|
||||
4
frontend/dist/.gitignore
vendored
Normal file
4
frontend/dist/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
@@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"watch": "vue-cli-service build --watch",
|
||||
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
|
||||
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean",
|
||||
"lint": "vue-cli-service lint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -69,13 +69,16 @@ nav > div {
|
||||
border-color: var(--divider);
|
||||
}
|
||||
|
||||
#breadcrumbs {
|
||||
.breadcrumbs {
|
||||
border-color: var(--divider);
|
||||
color: var(--textPrimary) !important;
|
||||
}
|
||||
#breadcrumbs span {
|
||||
.breadcrumbs span {
|
||||
color: var(--textPrimary) !important;
|
||||
}
|
||||
.breadcrumbs a:hover {
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
#listing .item {
|
||||
background: var(--surfacePrimary);
|
||||
@@ -114,13 +117,20 @@ nav > div {
|
||||
background: var(--surfaceSecondary);
|
||||
}
|
||||
|
||||
.dashboard #nav ul li {
|
||||
color: var(--textSecondary);
|
||||
}
|
||||
.dashboard #nav ul li:hover {
|
||||
background: var(--surfaceSecondary);
|
||||
}
|
||||
|
||||
.card h3,
|
||||
.dashboard #nav,
|
||||
.dashboard p label {
|
||||
color: var(--textPrimary);
|
||||
}
|
||||
.card#share ul li input,
|
||||
.card#share ul li select,
|
||||
.card#share input,
|
||||
.card#share select,
|
||||
.input {
|
||||
background: var(--surfaceSecondary);
|
||||
color: var(--textPrimary);
|
||||
@@ -138,7 +148,7 @@ nav > div {
|
||||
background: #147A41;
|
||||
}
|
||||
|
||||
.dashboard #nav li,
|
||||
.dashboard #nav .wrapper,
|
||||
.collapsible {
|
||||
border-color: var(--divider);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export async function put (url, content = '') {
|
||||
}
|
||||
|
||||
export function download (format, ...files) {
|
||||
let url = store.getters['isSharing'] ? `${baseURL}/api/public/dl/${store.state.hash}` : `${baseURL}/api/raw`
|
||||
let url = `${baseURL}/api/raw`
|
||||
|
||||
if (files.length === 1) {
|
||||
url += removePrefix(files[0]) + '?'
|
||||
@@ -74,15 +74,13 @@ export function download (format, ...files) {
|
||||
url += `/?files=${arg}&`
|
||||
}
|
||||
|
||||
if (format !== null) {
|
||||
if (format) {
|
||||
url += `algo=${format}&`
|
||||
}
|
||||
if (store.state.jwt !== ''){
|
||||
|
||||
if (store.state.jwt){
|
||||
url += `auth=${store.state.jwt}&`
|
||||
}
|
||||
if (store.state.token !== ''){
|
||||
url += `token=${store.state.token}`
|
||||
}
|
||||
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as files from './files'
|
||||
import * as share from './share'
|
||||
import * as users from './users'
|
||||
import * as settings from './settings'
|
||||
import * as pub from './pub'
|
||||
import search from './search'
|
||||
import commands from './commands'
|
||||
|
||||
@@ -10,6 +11,7 @@ export {
|
||||
share,
|
||||
users,
|
||||
settings,
|
||||
pub,
|
||||
commands,
|
||||
search
|
||||
}
|
||||
|
||||
61
frontend/src/api/pub.js
Normal file
61
frontend/src/api/pub.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { fetchURL, removePrefix } 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': password},
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
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 (item.isDir) {
|
||||
item.url += '/'
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
return data
|
||||
} else {
|
||||
throw new Error(res.status)
|
||||
}
|
||||
}
|
||||
|
||||
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) + ','
|
||||
}
|
||||
|
||||
arg = arg.substring(0, arg.length - 1)
|
||||
arg = encodeURIComponent(arg)
|
||||
url += `/?files=${arg}&`
|
||||
}
|
||||
|
||||
if (format) {
|
||||
url += `algo=${format}&`
|
||||
}
|
||||
|
||||
if (token) {
|
||||
url += `token=${token}&`
|
||||
}
|
||||
|
||||
window.open(url)
|
||||
}
|
||||
@@ -4,12 +4,6 @@ export async function list() {
|
||||
return fetchJSON('/api/shares')
|
||||
}
|
||||
|
||||
export async function getHash(hash, password = "") {
|
||||
return fetchJSON(`/api/public/share/${hash}`, {
|
||||
headers: {'X-SHARE-PASSWORD': password},
|
||||
})
|
||||
}
|
||||
|
||||
export async function get(url) {
|
||||
url = removePrefix(url)
|
||||
return fetchJSON(`/api/share${url}`)
|
||||
|
||||
@@ -34,11 +34,7 @@ export async function fetchJSON (url, opts) {
|
||||
}
|
||||
|
||||
export function removePrefix (url) {
|
||||
if (url.startsWith('/files')) {
|
||||
url = url.slice(6)
|
||||
} else if (store.getters['isSharing']) {
|
||||
url = url.slice(7 + store.state.hash.length)
|
||||
}
|
||||
url = url.split('/').splice(2).join('/')
|
||||
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
67
frontend/src/components/Breadcrumbs.vue
Normal file
67
frontend/src/components/Breadcrumbs.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="breadcrumbs">
|
||||
<component :is="element" :to="base || ''" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</component>
|
||||
|
||||
<span v-for="(link, index) in items" :key="index">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<component :is="element" :to="link.url">{{ link.name }}</component>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'breadcrumbs',
|
||||
props: [
|
||||
'base',
|
||||
'noLink'
|
||||
],
|
||||
computed: {
|
||||
items () {
|
||||
const relativePath = this.$route.path.replace(this.base, '')
|
||||
let parts = relativePath.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: this.base + '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
}
|
||||
}
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
},
|
||||
element () {
|
||||
if (this.noLink !== undefined) {
|
||||
return 'span'
|
||||
}
|
||||
|
||||
return 'router-link'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,185 +0,0 @@
|
||||
<template>
|
||||
<header v-if="!isEditor && !isPreview">
|
||||
<div>
|
||||
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
|
||||
<i class="material-icons">menu</i>
|
||||
</button>
|
||||
<img :src="logoURL" alt="File Browser">
|
||||
<search v-if="isLogged"></search>
|
||||
</div>
|
||||
<div>
|
||||
<template v-if="isLogged || isSharing">
|
||||
<button v-show="!isSharing" @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action">
|
||||
<i class="material-icons">search</i>
|
||||
</button>
|
||||
|
||||
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</button>
|
||||
|
||||
<!-- Menu that shows on listing AND mobile when there are files selected -->
|
||||
<div id="file-selection" v-if="isMobile && isListing && !isSharing">
|
||||
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
|
||||
<share-button v-show="showShareButton"></share-button>
|
||||
<rename-button v-show="showRenameButton"></rename-button>
|
||||
<copy-button v-show="showCopyButton"></copy-button>
|
||||
<move-button v-show="showMoveButton"></move-button>
|
||||
<delete-button v-show="showDeleteButton"></delete-button>
|
||||
</div>
|
||||
|
||||
<!-- This buttons are shown on a dropdown on mobile phones -->
|
||||
<div id="dropdown" :class="{ active: showMore }">
|
||||
<div v-if="!isListing || !isMobile">
|
||||
<share-button v-show="showShareButton"></share-button>
|
||||
<rename-button v-show="showRenameButton"></rename-button>
|
||||
<copy-button v-show="showCopyButton"></copy-button>
|
||||
<move-button v-show="showMoveButton"></move-button>
|
||||
<delete-button v-show="showDeleteButton"></delete-button>
|
||||
</div>
|
||||
|
||||
<shell-button v-if="isExecEnabled && !isSharing && user.perm.execute" />
|
||||
<switch-button v-show="isListing"></switch-button>
|
||||
<download-button v-show="showDownloadButton"></download-button>
|
||||
<upload-button v-show="showUpload"></upload-button>
|
||||
<info-button v-show="isFiles"></info-button>
|
||||
|
||||
<button v-show="isListing || (isSharing && req.isDir)" @click="toggleMultipleSelection" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" >
|
||||
<i class="material-icons">check_circle</i>
|
||||
<span>{{ $t('buttons.select') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from './Search'
|
||||
import InfoButton from './buttons/Info'
|
||||
import DeleteButton from './buttons/Delete'
|
||||
import RenameButton from './buttons/Rename'
|
||||
import UploadButton from './buttons/Upload'
|
||||
import DownloadButton from './buttons/Download'
|
||||
import SwitchButton from './buttons/SwitchView'
|
||||
import MoveButton from './buttons/Move'
|
||||
import CopyButton from './buttons/Copy'
|
||||
import ShareButton from './buttons/Share'
|
||||
import ShellButton from './buttons/Shell'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { logoURL, enableExec } from '@/utils/constants'
|
||||
import * as api from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'header-layout',
|
||||
components: {
|
||||
Search,
|
||||
InfoButton,
|
||||
DeleteButton,
|
||||
ShareButton,
|
||||
RenameButton,
|
||||
DownloadButton,
|
||||
CopyButton,
|
||||
UploadButton,
|
||||
SwitchButton,
|
||||
MoveButton,
|
||||
ShellButton
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
pluginData: {
|
||||
api,
|
||||
buttons,
|
||||
'store': this.$store,
|
||||
'router': this.$router
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('resize', () => {
|
||||
this.width = window.innerWidth
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'selectedCount',
|
||||
'isFiles',
|
||||
'isEditor',
|
||||
'isPreview',
|
||||
'isListing',
|
||||
'isLogged',
|
||||
'isSharing'
|
||||
]),
|
||||
...mapState([
|
||||
'req',
|
||||
'user',
|
||||
'loading',
|
||||
'reload',
|
||||
'multiple'
|
||||
]),
|
||||
logoURL: () => logoURL,
|
||||
isExecEnabled: () => enableExec,
|
||||
isMobile () {
|
||||
return this.width <= 736
|
||||
},
|
||||
showUpload () {
|
||||
return this.isListing && this.user.perm.create
|
||||
},
|
||||
showDownloadButton () {
|
||||
return (this.isFiles && this.user.perm.download) || (this.isSharing && this.selectedCount > 0)
|
||||
},
|
||||
showDeleteButton () {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount !== 0 && this.user.perm.delete)
|
||||
: this.user.perm.delete)
|
||||
},
|
||||
showRenameButton () {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount === 1 && this.user.perm.rename)
|
||||
: this.user.perm.rename)
|
||||
},
|
||||
showShareButton () {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount === 1 && this.user.perm.share)
|
||||
: this.user.perm.share)
|
||||
},
|
||||
showMoveButton () {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount > 0 && this.user.perm.rename)
|
||||
: this.user.perm.rename)
|
||||
},
|
||||
showCopyButton () {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount > 0 && this.user.perm.create)
|
||||
: this.user.perm.create)
|
||||
},
|
||||
showMore () {
|
||||
return (this.isFiles || this.isSharing) && this.$store.state.show === 'more'
|
||||
},
|
||||
showOverlay () {
|
||||
return this.showMore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openMore () {
|
||||
this.$store.commit('showHover', 'more')
|
||||
},
|
||||
openSearch () {
|
||||
this.$store.commit('showHover', 'search')
|
||||
},
|
||||
toggleMultipleSelection () {
|
||||
this.$store.commit('multiple', !this.multiple)
|
||||
this.resetPrompts()
|
||||
},
|
||||
resetPrompts () {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
|
||||
<i class="material-icons">content_copy</i>
|
||||
<span>{{ $t('buttons.copyFile') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'copy-button',
|
||||
methods: {
|
||||
show: function () {
|
||||
this.$store.commit('showHover', 'copy')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
|
||||
<i class="material-icons">delete</i>
|
||||
<span>{{ $t('buttons.delete') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'delete-button',
|
||||
methods: {
|
||||
show: function () {
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
|
||||
<i class="material-icons">file_download</i>
|
||||
<span>{{ $t('buttons.download') }}</span>
|
||||
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'download-button',
|
||||
computed: {
|
||||
...mapState(['req', 'selected']),
|
||||
...mapGetters(['isListing', 'selectedCount', 'isSharing'])
|
||||
},
|
||||
methods: {
|
||||
download: function () {
|
||||
if (!this.isListing && !this.isSharing) {
|
||||
api.download(null, this.$route.path)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.req.items[this.selected[0]].url)
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.commit('showHover', 'download')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
|
||||
<i class="material-icons">info</i>
|
||||
<span>{{ $t('buttons.info') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'info-button',
|
||||
methods: {
|
||||
show: function () {
|
||||
this.$store.commit('showHover', 'info')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
|
||||
<i class="material-icons">forward</i>
|
||||
<span>{{ $t('buttons.moveFile') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'move-button',
|
||||
methods: {
|
||||
show: function () {
|
||||
this.$store.commit('showHover', 'move')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="$emit('change-size')">
|
||||
<i class="material-icons">{{ this.icon }}</i>
|
||||
<span>{{ $t('buttons.info') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'preview-size-button',
|
||||
props: [ 'size' ],
|
||||
computed: {
|
||||
icon () {
|
||||
if (this.size) {
|
||||
return 'photo_size_select_large'
|
||||
}
|
||||
|
||||
return 'hd'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
|
||||
<i class="material-icons">mode_edit</i>
|
||||
<span>{{ $t('buttons.rename') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'rename-button',
|
||||
methods: {
|
||||
show: function () {
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.share')" :title="$t('buttons.share')" class="action">
|
||||
<i class="material-icons">share</i>
|
||||
<span>{{ $t('buttons.share') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'share-button',
|
||||
methods: {
|
||||
show () {
|
||||
this.$store.commit('showHover', 'share')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.shell')" :title="$t('buttons.shell')" class="action">
|
||||
<i class="material-icons">code</i>
|
||||
<span>{{ $t('buttons.shell') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'shell-button',
|
||||
methods: {
|
||||
show: function () {
|
||||
this.$store.commit('toggleShell')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" id="switch-view-button">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<span>{{ $t('buttons.switchView') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { users as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'switch-button',
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
icon: function () {
|
||||
if (this.user.viewMode === 'mosaic') return 'view_list'
|
||||
return 'view_module'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'updateUser', 'closeHovers' ]),
|
||||
change: async function () {
|
||||
this.closeHovers()
|
||||
|
||||
const data = {
|
||||
id: this.user.id,
|
||||
viewMode: (this.icon === 'view_list') ? 'list' : 'mosaic'
|
||||
}
|
||||
|
||||
try {
|
||||
await api.update(data, ['viewMode'])
|
||||
this.updateUser(data)
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
|
||||
<i class="material-icons">file_upload</i>
|
||||
<span>{{ $t('buttons.upload') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'upload-button',
|
||||
methods: {
|
||||
upload: function () {
|
||||
if (typeof(DataTransferItem.prototype.webkitGetAsEntry) !== 'undefined') {
|
||||
this.$store.commit('showHover', 'upload')
|
||||
} else {
|
||||
document.getElementById('upload-input').click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -13,7 +13,7 @@
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected">
|
||||
<div>
|
||||
<img v-if="type==='image' && isThumbsEnabled && !isSharing" v-lazy="thumbnailUrl">
|
||||
<img v-if="readOnly == undefined && type==='image' && isThumbsEnabled" v-lazy="thumbnailUrl">
|
||||
<i v-else class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
|
||||
@@ -45,13 +45,12 @@ export default {
|
||||
touches: 0
|
||||
}
|
||||
},
|
||||
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
|
||||
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index', 'readOnly'],
|
||||
computed: {
|
||||
...mapState(['user', 'selected', 'req', 'jwt']),
|
||||
...mapGetters(['selectedCount', 'isSharing']),
|
||||
...mapGetters(['selectedCount']),
|
||||
singleClick () {
|
||||
if (this.isSharing) return false
|
||||
return this.user.singleClick
|
||||
return this.readOnly == undefined && this.user.singleClick
|
||||
},
|
||||
isSelected () {
|
||||
return (this.selected.indexOf(this.index) !== -1)
|
||||
@@ -64,10 +63,10 @@ export default {
|
||||
return 'insert_drive_file'
|
||||
},
|
||||
isDraggable () {
|
||||
return !this.isSharing && this.user.perm.rename
|
||||
return this.readOnly == undefined && this.user.perm.rename
|
||||
},
|
||||
canDrop () {
|
||||
if (!this.isDir || this.isSharing) return false
|
||||
if (!this.isDir || this.readOnly !== undefined) return false
|
||||
|
||||
for (let i of this.selected) {
|
||||
if (this.req.items[i].url === this.url) {
|
||||
@@ -79,7 +78,11 @@ export default {
|
||||
},
|
||||
thumbnailUrl () {
|
||||
const path = this.url.replace(/^\/files\//, '')
|
||||
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true`
|
||||
|
||||
// reload the image when the file is replaced
|
||||
const key = Date.parse(this.modified)
|
||||
|
||||
return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true&k=${key}`
|
||||
},
|
||||
isThumbsEnabled () {
|
||||
return enableThumbs
|
||||
@@ -139,7 +142,7 @@ export default {
|
||||
to: this.url + this.req.items[i].name,
|
||||
name: this.req.items[i].name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let base = el.querySelector('.name').innerHTML + '/'
|
||||
let path = this.$route.path + base
|
||||
|
||||
32
frontend/src/components/header/Action.vue
Normal file
32
frontend/src/components/header/Action.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<button @click="action" :aria-label="label" :title="label" class="action">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="counter > 0" class="counter">{{ counter }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'action',
|
||||
props: [
|
||||
'icon',
|
||||
'label',
|
||||
'counter',
|
||||
'show'
|
||||
],
|
||||
methods: {
|
||||
action: function () {
|
||||
if (this.show) {
|
||||
this.$store.commit('showHover', this.show)
|
||||
}
|
||||
|
||||
this.$emit('action')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
47
frontend/src/components/header/HeaderBar.vue
Normal file
47
frontend/src/components/header/HeaderBar.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<header>
|
||||
<img v-if="showLogo !== undefined" :src="logoURL" />
|
||||
<action v-if="showMenu !== undefined" class="menu-button" icon="menu" :label="$t('buttons.toggleSidebar')" @action="openSidebar()" />
|
||||
|
||||
<slot />
|
||||
|
||||
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
<action v-if="this.$slots.actions" id="more" icon="more_vert" :label="$t('buttons.more')" @action="$store.commit('showHover', 'more')" />
|
||||
|
||||
<div class="overlay" v-show="this.$store.state.show == 'more'" @click="$store.commit('closeHovers')" />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { logoURL } from '@/utils/constants'
|
||||
|
||||
import Action from '@/components/header/Action'
|
||||
|
||||
export default {
|
||||
name: 'header-bar',
|
||||
props: [
|
||||
'showLogo',
|
||||
'showMenu',
|
||||
],
|
||||
components: {
|
||||
Action
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
logoURL
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
name: 'delete',
|
||||
computed: {
|
||||
...mapGetters(['isListing', 'selectedCount']),
|
||||
...mapState(['req', 'selected'])
|
||||
...mapState(['req', 'selected', 'showConfirm'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['closeHovers']),
|
||||
@@ -38,7 +38,7 @@ export default {
|
||||
await api.remove(this.$route.path)
|
||||
buttons.success('delete')
|
||||
|
||||
this.$root.$emit('preview-deleted')
|
||||
this.showConfirm()
|
||||
this.closeHovers()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,43 +7,29 @@
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.downloadMessage') }}</p>
|
||||
|
||||
<button class="button button--block" @click="download('zip')" v-focus>zip</button>
|
||||
<button class="button button--block" @click="download('tar')" v-focus>tar</button>
|
||||
<button class="button button--block" @click="download('targz')" v-focus>tar.gz</button>
|
||||
<button class="button button--block" @click="download('tarbz2')" v-focus>tar.bz2</button>
|
||||
<button class="button button--block" @click="download('tarxz')" v-focus>tar.xz</button>
|
||||
<button class="button button--block" @click="download('tarlz4')" v-focus>tar.lz4</button>
|
||||
<button class="button button--block" @click="download('tarsz')" v-focus>tar.sz</button>
|
||||
<button v-for="(ext, format) in formats" :key="format" class="button button--block" @click="showConfirm(format)" v-focus>{{ ext }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'download',
|
||||
computed: {
|
||||
...mapState(['selected', 'req']),
|
||||
...mapGetters(['selectedCount'])
|
||||
},
|
||||
methods: {
|
||||
download: function (format) {
|
||||
if (this.selectedCount === 0) {
|
||||
api.download(format, this.$route.path)
|
||||
} else {
|
||||
let files = []
|
||||
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].url)
|
||||
}
|
||||
|
||||
api.download(format, ...files)
|
||||
data: function () {
|
||||
return {
|
||||
formats: {
|
||||
zip: 'zip',
|
||||
tar: 'tar',
|
||||
targz: 'tar.gz',
|
||||
tarbz2: 'tar.bz2',
|
||||
tarxz: 'tar.xz',
|
||||
tarlz4: 'tar.lz4',
|
||||
tarsz: 'tar.sz'
|
||||
}
|
||||
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: mapState(['showConfirm'])
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
return moment(this.req.modified).fromNow()
|
||||
}
|
||||
|
||||
return moment(this.req.items[this.selected[0]]).fromNow()
|
||||
return moment(this.req.items[this.selected[0]].modified).fromNow()
|
||||
},
|
||||
name: function () {
|
||||
return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name
|
||||
|
||||
@@ -4,61 +4,82 @@
|
||||
<h2>{{ $t('buttons.share') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<ul>
|
||||
<template v-if="listing">
|
||||
<div class="card-content">
|
||||
<table>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{{ $t('settings.shareDuration') }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<li v-for="link in links" :key="link.hash">
|
||||
<a :href="buildLink(link.hash)" target="_blank">
|
||||
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
|
||||
<template v-else>{{ $t('permanent') }}</template>
|
||||
</a>
|
||||
<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-else>{{ $t('permanent') }}</template>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button class="action"
|
||||
@click="deleteLink($event, link)"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button class="action"
|
||||
@click="deleteLink($event, link)"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
||||
<button class="button button--flat button--blue"
|
||||
@click="() => switchListing()"
|
||||
:aria-label="$t('buttons.new')"
|
||||
:title="$t('buttons.new')">{{ $t('buttons.new') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
</li>
|
||||
<template v-else>
|
||||
<div class="card-content">
|
||||
<p>{{ $t('settings.shareDuration') }}</p>
|
||||
<div class="input-group input">
|
||||
<input v-focus
|
||||
type="number"
|
||||
max="2147483647"
|
||||
min="1"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="time">
|
||||
<select class="right" v-model="unit" :aria-label="$t('time.unit')">
|
||||
<option value="seconds">{{ $t('time.seconds') }}</option>
|
||||
<option value="minutes">{{ $t('time.minutes') }}</option>
|
||||
<option value="hours">{{ $t('time.hours') }}</option>
|
||||
<option value="days">{{ $t('time.days') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<p>{{ $t('prompts.optionalPassword') }}</p>
|
||||
<input class="input input--block" type="password" v-model.trim="password">
|
||||
</div>
|
||||
|
||||
<li v-if="!hasPermanent">
|
||||
<div>
|
||||
<input type="password" :placeholder="$t('prompts.optionalPassword')" v-model="passwordPermalink">
|
||||
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input v-focus
|
||||
type="number"
|
||||
max="2147483647"
|
||||
min="0"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="time">
|
||||
<select v-model="unit" :aria-label="$t('time.unit')">
|
||||
<option value="seconds">{{ $t('time.seconds') }}</option>
|
||||
<option value="minutes">{{ $t('time.minutes') }}</option>
|
||||
<option value="hours">{{ $t('time.hours') }}</option>
|
||||
<option value="days">{{ $t('time.days') }}</option>
|
||||
</select>
|
||||
<input type="password" :placeholder="$t('prompts.optionalPassword')" v-model="password">
|
||||
<button class="action"
|
||||
@click="submit"
|
||||
:aria-label="$t('buttons.create')"
|
||||
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="() => switchListing()"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat button--blue"
|
||||
@click="submit"
|
||||
:aria-label="$t('buttons.share')"
|
||||
:title="$t('buttons.share')">{{ $t('buttons.share') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -75,11 +96,10 @@ export default {
|
||||
return {
|
||||
time: '',
|
||||
unit: 'hours',
|
||||
hasPermanent: false,
|
||||
links: [],
|
||||
clip: null,
|
||||
password: '',
|
||||
passwordPermalink: ''
|
||||
listing: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -104,11 +124,8 @@ export default {
|
||||
this.links = links
|
||||
this.sort()
|
||||
|
||||
for (let link of this.links) {
|
||||
if (link.expire === 0) {
|
||||
this.hasPermanent = true
|
||||
break
|
||||
}
|
||||
if (this.links.length == 0) {
|
||||
this.listing = false
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
@@ -125,22 +142,25 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
submit: async function () {
|
||||
if (!this.time) return
|
||||
let isPermanent = !this.time || this.time == 0
|
||||
|
||||
try {
|
||||
const res = await api.create(this.url, this.password, this.time, this.unit)
|
||||
let res = null
|
||||
|
||||
if (isPermanent) {
|
||||
res = await api.create(this.url, this.password)
|
||||
} else {
|
||||
res = await api.create(this.url, this.password, this.time, this.unit)
|
||||
}
|
||||
|
||||
this.links.push(res)
|
||||
this.sort()
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
getPermalink: async function () {
|
||||
try {
|
||||
const res = await api.create(this.url, this.passwordPermalink)
|
||||
this.links.push(res)
|
||||
this.sort()
|
||||
this.hasPermanent = true
|
||||
|
||||
this.time = ''
|
||||
this.unit = 'hours'
|
||||
this.password = ''
|
||||
|
||||
this.listing = true
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
}
|
||||
@@ -149,8 +169,11 @@ export default {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await api.remove(link.hash)
|
||||
if (link.expire === 0) this.hasPermanent = false
|
||||
this.links = this.links.filter(item => item.hash !== link.hash)
|
||||
|
||||
if (this.links.length == 0) {
|
||||
this.listing = false
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
}
|
||||
@@ -167,6 +190,13 @@ export default {
|
||||
if (b.expire === 0) return 1
|
||||
return new Date(a.expire) - new Date(b.expire)
|
||||
})
|
||||
},
|
||||
switchListing () {
|
||||
if (this.links.length == 0 && !this.listing) {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
|
||||
this.listing = !this.listing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.deleteMessageShare', {path: hash.path}) }}</p>
|
||||
<p>{{ $t('prompts.deleteMessageShare', {path: ''}) }}</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button @click="$store.commit('closeHovers')"
|
||||
@@ -17,30 +17,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapMutations, mapState} from 'vuex'
|
||||
import { share as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'share-delete',
|
||||
computed: {
|
||||
...mapState(['hash'])
|
||||
...mapState(['showConfirm'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['closeHovers']),
|
||||
submit: async function () {
|
||||
buttons.loading('delete')
|
||||
|
||||
try {
|
||||
await api.remove(this.hash.hash)
|
||||
buttons.success('delete')
|
||||
|
||||
this.$root.$emit('share-deleted', this.hash.hash)
|
||||
this.closeHovers()
|
||||
} catch (e) {
|
||||
buttons.done('delete')
|
||||
this.$showError(e)
|
||||
}
|
||||
submit: function () {
|
||||
this.showConfirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,4 @@
|
||||
padding: .5em;
|
||||
text-align: center;
|
||||
animation: .2s opac forwards;
|
||||
}
|
||||
|
||||
.share__promt__card {
|
||||
max-width: max-content !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
@@ -83,29 +83,29 @@ main {
|
||||
width: calc(100% - 19em);
|
||||
}
|
||||
|
||||
#breadcrumbs {
|
||||
.breadcrumbs {
|
||||
height: 3em;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
#breadcrumbs span,
|
||||
#breadcrumbs {
|
||||
.breadcrumbs span,
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #6f6f6f;
|
||||
}
|
||||
|
||||
#breadcrumbs a {
|
||||
.breadcrumbs a {
|
||||
color: inherit;
|
||||
transition: .1s ease-in;
|
||||
border-radius: .125em;
|
||||
}
|
||||
|
||||
#breadcrumbs a:hover {
|
||||
.breadcrumbs a:hover {
|
||||
background-color: rgba(0,0,0, 0.05);
|
||||
}
|
||||
|
||||
#breadcrumbs span a {
|
||||
.breadcrumbs span a {
|
||||
padding: .2em;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
.dashboard {
|
||||
max-width: 600px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.dashboard .row {
|
||||
display: flex;
|
||||
margin: 0 -.5em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard .row .column {
|
||||
display: flex;
|
||||
padding: 0 .5em;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.dashboard .row .column .card {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media(max-width: 1200px) {
|
||||
.dashboard .row .column {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit
|
||||
}
|
||||
@@ -28,25 +49,56 @@ p code {
|
||||
}
|
||||
|
||||
.dashboard #nav {
|
||||
display: flex;
|
||||
padding-bottom: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dashboard #nav .wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dashboard #nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
color: rgb(84, 110, 122);
|
||||
font-weight: 500;
|
||||
margin: 0 0 1em;
|
||||
padding: 0;
|
||||
margin: 0 0 -2px 0;
|
||||
font-size: .8em;
|
||||
text-align: center;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.dashboard #nav li {
|
||||
.dashboard #nav ul li {
|
||||
position: relative;
|
||||
padding: 1.5em 2em;
|
||||
white-space: nowrap;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: .1s ease-in-out all;
|
||||
|
||||
}
|
||||
|
||||
.dashboard #nav ul li:hover {
|
||||
background: var(--moon-grey);
|
||||
}
|
||||
|
||||
.dashboard #nav ul li.active {
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.dashboard #nav ul li.active::before {
|
||||
width: 100%;
|
||||
padding: 0 0 1em;
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dashboard #nav li.active {
|
||||
border-color: var(--blue)
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: "";
|
||||
background: var(--blue);
|
||||
opacity: 0.08;
|
||||
}
|
||||
|
||||
.dashboard #nav i {
|
||||
@@ -92,7 +144,7 @@ table tr>*:last-child {
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
margin: .5rem 0 1rem 0;
|
||||
margin: 0 0 1rem 0;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
@@ -151,6 +203,7 @@ table tr>*:last-child {
|
||||
|
||||
.card .card-content.full {
|
||||
padding-bottom: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
@@ -226,6 +279,18 @@ table tr>*:last-child {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card#share .input-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card#share .input-group * {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card#share .input-group input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
|
||||
@@ -6,9 +6,25 @@ header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4em;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
padding: 0.5em 0.5em 0.5em 1em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header > * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
header title {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 1em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
header .overlay {
|
||||
@@ -30,17 +46,6 @@ header img {
|
||||
height: 2.5em;
|
||||
}
|
||||
|
||||
header>div:first-child>.action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header>div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0.5em 0.5em 0.5em 1em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header .action span {
|
||||
display: none;
|
||||
}
|
||||
@@ -50,19 +55,8 @@ header>div div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header>div:last-child div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
header>div:first-child {
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
header>div:last-child {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
header .search-button {
|
||||
header .search-button,
|
||||
header .menu-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||
width: 95%;
|
||||
max-width: 20em;
|
||||
z-index: 1;
|
||||
}
|
||||
#file-selection .action {
|
||||
border-radius: 50%;
|
||||
@@ -81,6 +82,9 @@
|
||||
color: #6f6f6f;
|
||||
margin-right: auto;
|
||||
}
|
||||
#file-selection .action span {
|
||||
display: none;
|
||||
}
|
||||
nav {
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
@@ -95,7 +99,7 @@
|
||||
left: 0;
|
||||
}
|
||||
header .search-button,
|
||||
header>div:first-child>.action {
|
||||
header .menu-button {
|
||||
display: inherit;
|
||||
}
|
||||
header img {
|
||||
|
||||
@@ -96,10 +96,11 @@
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: .75em;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
text-align: center;
|
||||
line-height: 1.25em;
|
||||
line-height: 1.55em;
|
||||
font-weight: bold;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
@@ -117,45 +118,33 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#previewer .bar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
height: 3.7em;
|
||||
}
|
||||
|
||||
#previewer .bar > * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#previewer .bar .title {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 1em;
|
||||
line-height: 2.3em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 1.2em;
|
||||
#previewer header {
|
||||
background: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#previewer .action i {
|
||||
#previewer header > .action i {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#previewer .action:hover {
|
||||
@media (min-width: 738px) {
|
||||
#previewer header #dropdown .action i {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
#previewer header .action:hover {
|
||||
background-color: rgba(255, 255, 255, 0.3)
|
||||
}
|
||||
|
||||
#previewer .action span {
|
||||
#previewer header .action span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#previewer .preview {
|
||||
margin: 2em auto 4em;
|
||||
max-width: 80%;
|
||||
margin-top: 4em;
|
||||
text-align: center;
|
||||
height: calc(100vh - 9.7em);
|
||||
height: calc(100vh - 4em);
|
||||
}
|
||||
|
||||
#previewer .preview pre {
|
||||
@@ -170,6 +159,10 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#previewer .preview video {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#previewer .pdf {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -182,8 +175,25 @@
|
||||
#previewer>button {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
top: calc(50% + 1.85em);
|
||||
transform: translateY(-50%);
|
||||
background-color: rgba(80, 80, 80, .5);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
transition: 0.2s ease all;
|
||||
}
|
||||
|
||||
#previewer>button.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#previewer>button>i {
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
#previewer>button:first-of-type {
|
||||
@@ -199,6 +209,7 @@
|
||||
#editor-container {
|
||||
background-color: #fafafa;
|
||||
position: fixed;
|
||||
margin-top: 4em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@@ -206,43 +217,28 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#editor-container .bar {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
height: 3.7em;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#editor-container .title {
|
||||
margin-right: auto;
|
||||
padding: 0 1em;
|
||||
line-height: 2.7em;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#previewer .loading {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#editor-container #editor {
|
||||
height: calc(100vh - 8.2em);
|
||||
height: calc(100vh - 8.4em);
|
||||
}
|
||||
|
||||
#editor-container #breadcrumbs {
|
||||
#editor-container .breadcrumbs {
|
||||
height: 2.3em;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
#editor-container #breadcrumbs span {
|
||||
#editor-container .breadcrumbs span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#editor-container .breadcrumbs i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* PROMPT *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"create": "创建",
|
||||
"delete": "删除",
|
||||
"download": "下载",
|
||||
"hideDotfiles": "",
|
||||
"hideDotfiles": "不显示隐藏的文件",
|
||||
"info": "信息",
|
||||
"more": "更多",
|
||||
"move": "移动",
|
||||
@@ -29,6 +29,7 @@
|
||||
"selectMultiple": "选择多个",
|
||||
"share": "分享",
|
||||
"shell": "激活 shell",
|
||||
"submit": "提交",
|
||||
"switchView": "切换显示方式",
|
||||
"toggleSidebar": "切换侧边栏",
|
||||
"update": "更新",
|
||||
@@ -138,11 +139,12 @@
|
||||
"replace": "替换",
|
||||
"replaceMessage": "您尝试上传的文件中有一个与现有文件的名称存在冲突。是否替换现有的同名文件?",
|
||||
"schedule": "计划",
|
||||
"scheduleMessage": "请选择发布这篇帖子的日期。",
|
||||
"scheduleMessage": "请选择发布这篇帖子的日期与时间。",
|
||||
"show": "点击以显示",
|
||||
"size": "大小",
|
||||
"upload": "上传",
|
||||
"uploadMessage": "选择上传项。"
|
||||
"uploadMessage": "选择上传选项。",
|
||||
"optionalPassword": "密码(选填,不填即无密码)"
|
||||
},
|
||||
"search": {
|
||||
"images": "图像",
|
||||
|
||||
@@ -10,9 +10,7 @@ import Settings from '@/views/Settings'
|
||||
import GlobalSettings from '@/views/settings/Global'
|
||||
import ProfileSettings from '@/views/settings/Profile'
|
||||
import Shares from '@/views/settings/Shares'
|
||||
import Error403 from '@/views/errors/403'
|
||||
import Error404 from '@/views/errors/404'
|
||||
import Error500 from '@/views/errors/500'
|
||||
import Errors from '@/views/Errors'
|
||||
import store from '@/store'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
|
||||
@@ -102,17 +100,29 @@ const router = new Router({
|
||||
{
|
||||
path: '/403',
|
||||
name: 'Forbidden',
|
||||
component: Error403
|
||||
component: Errors,
|
||||
props: {
|
||||
errorCode: 403,
|
||||
showHeader: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: 'Not Found',
|
||||
component: Error404
|
||||
component: Errors,
|
||||
props: {
|
||||
errorCode: 404,
|
||||
showHeader: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: 'Internal Server Error',
|
||||
component: Error500
|
||||
component: Errors,
|
||||
props: {
|
||||
errorCode: 500,
|
||||
showHeader: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/files',
|
||||
|
||||
@@ -2,9 +2,6 @@ const getters = {
|
||||
isLogged: state => state.user !== null,
|
||||
isFiles: state => !state.loading && state.route.name === 'Files',
|
||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
||||
isEditor: (state, getters) => getters.isFiles && (state.req.type === 'text' || state.req.type === 'textImmutable'),
|
||||
isPreview: state => state.previewMode,
|
||||
isSharing: state => !state.loading && state.route.name === 'Share',
|
||||
selectedCount: state => state.selected.length,
|
||||
progress : state => {
|
||||
if (state.upload.progress.length == 0) {
|
||||
|
||||
@@ -22,11 +22,7 @@ const state = {
|
||||
multiple: false,
|
||||
show: null,
|
||||
showShell: false,
|
||||
showMessage: null,
|
||||
showConfirm: null,
|
||||
previewMode: false,
|
||||
hash: '',
|
||||
token: '',
|
||||
showConfirm: null
|
||||
}
|
||||
|
||||
export default new Vuex.Store({
|
||||
|
||||
@@ -4,7 +4,7 @@ import moment from 'moment'
|
||||
const mutations = {
|
||||
closeHovers: state => {
|
||||
state.show = null
|
||||
state.showMessage = null
|
||||
state.showConfirm = null
|
||||
},
|
||||
toggleShell: (state) => {
|
||||
state.showShell = !state.showShell
|
||||
@@ -16,16 +16,13 @@ const mutations = {
|
||||
}
|
||||
|
||||
state.show = value.prompt
|
||||
state.showMessage = value.message
|
||||
state.showConfirm = value.confirm
|
||||
},
|
||||
showError: (state, value) => {
|
||||
showError: (state) => {
|
||||
state.show = 'error'
|
||||
state.showMessage = value
|
||||
},
|
||||
showSuccess: (state, value) => {
|
||||
showSuccess: (state) => {
|
||||
state.show = 'success'
|
||||
state.showMessage = value
|
||||
},
|
||||
setLoading: (state, value) => { state.loading = value },
|
||||
setReload: (state, value) => { state.reload = value },
|
||||
@@ -46,12 +43,8 @@ const mutations = {
|
||||
state.user = value
|
||||
},
|
||||
setJWT: (state, value) => (state.jwt = value),
|
||||
setToken: (state, value ) => (state.token = value),
|
||||
multiple: (state, value) => (state.multiple = value),
|
||||
addSelected: (state, value) => (state.selected.push(value)),
|
||||
addPlugin: (state, value) => {
|
||||
state.plugins.push(value)
|
||||
},
|
||||
removeSelected: (state, value) => {
|
||||
let i = state.selected.indexOf(value)
|
||||
if (i === -1) return
|
||||
@@ -84,11 +77,7 @@ const mutations = {
|
||||
resetClipboard: (state) => {
|
||||
state.clipboard.key = ''
|
||||
state.clipboard.items = []
|
||||
},
|
||||
setPreviewMode(state, value) {
|
||||
state.previewMode = value
|
||||
},
|
||||
setHash: (state, value) => (state.hash = value),
|
||||
}
|
||||
}
|
||||
|
||||
export default mutations
|
||||
|
||||
@@ -6,6 +6,10 @@ function loading (button) {
|
||||
return
|
||||
}
|
||||
|
||||
if (el.innerHTML == 'autorenew' || el.innerHTML == 'done') {
|
||||
return
|
||||
}
|
||||
|
||||
el.dataset.icon = el.innerHTML
|
||||
el.style.opacity = 0
|
||||
|
||||
|
||||
46
frontend/src/views/Errors.vue
Normal file
46
frontend/src/views/Errors.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<header-bar v-if="showHeader" showMenu showLogo />
|
||||
|
||||
<h2 class="message">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<span>{{ message }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
|
||||
const errors = {
|
||||
403: {
|
||||
icon: 'error',
|
||||
message: 'errors.forbidden'
|
||||
},
|
||||
404: {
|
||||
icon: 'gps_off',
|
||||
message: 'errors.notFound'
|
||||
},
|
||||
500: {
|
||||
icon: 'error_outline',
|
||||
message: 'errors.internal'
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'errors',
|
||||
components: {
|
||||
HeaderBar
|
||||
},
|
||||
props: [
|
||||
'errorCode', 'showHeader'
|
||||
],
|
||||
data: function () {
|
||||
return {
|
||||
icon: errors[this.errorCode].icon,
|
||||
message: this.$t(errors[this.errorCode].message)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,24 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="breadcrumbs" v-if="isListing || error">
|
||||
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</router-link>
|
||||
<header-bar v-if="error || !req.type" showMenu showLogo />
|
||||
|
||||
<span v-for="(link, index) in breadcrumbs" :key="index">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<router-link :to="link.url">{{ link.name }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<breadcrumbs base="/files" />
|
||||
|
||||
<div v-if="error">
|
||||
<not-found v-if="error.message === '404'"></not-found>
|
||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||
<internal-error v-else></internal-error>
|
||||
</div>
|
||||
<preview v-else-if="isPreview"></preview>
|
||||
<editor v-else-if="isEditor"></editor>
|
||||
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||
<errors v-if="error" :errorCode="errorCode" />
|
||||
<component v-else-if="currentView" :is="currentView"></component>
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
@@ -28,13 +15,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Forbidden from './errors/403'
|
||||
import NotFound from './errors/404'
|
||||
import InternalError from './errors/500'
|
||||
import Preview from '@/components/files/Preview'
|
||||
import Listing from '@/components/files/Listing'
|
||||
import { files as api } from '@/api'
|
||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
import Errors from '@/views/Errors'
|
||||
import Preview from '@/views/files/Preview'
|
||||
import Listing from '@/views/files/Listing'
|
||||
|
||||
function clean (path) {
|
||||
return path.endsWith('/') ? path.slice(0, -1) : path
|
||||
@@ -43,68 +31,41 @@ function clean (path) {
|
||||
export default {
|
||||
name: 'files',
|
||||
components: {
|
||||
Forbidden,
|
||||
NotFound,
|
||||
InternalError,
|
||||
HeaderBar,
|
||||
Breadcrumbs,
|
||||
Errors,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor: () => import('@/components/files/Editor')
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'selectedCount',
|
||||
'isListing',
|
||||
'isEditor',
|
||||
'isFiles'
|
||||
]),
|
||||
...mapState([
|
||||
'req',
|
||||
'user',
|
||||
'reload',
|
||||
'multiple',
|
||||
'loading',
|
||||
'show'
|
||||
]),
|
||||
isPreview () {
|
||||
return !this.loading && !this.isListing && !this.isEditor || this.loading && this.$store.state.previewMode
|
||||
},
|
||||
breadcrumbs () {
|
||||
let parts = this.$route.path.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
}
|
||||
}
|
||||
|
||||
breadcrumbs.shift()
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
Editor: () => import('@/views/files/Editor'),
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null
|
||||
error: null,
|
||||
width: window.innerWidth
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'req',
|
||||
'reload',
|
||||
'loading',
|
||||
'show'
|
||||
]),
|
||||
currentView () {
|
||||
if (this.req.type == undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (this.req.isDir) {
|
||||
return 'listing'
|
||||
} else if(this.req.type === 'text' || this.req.type === 'textImmutable') {
|
||||
return 'editor'
|
||||
} else {
|
||||
return 'preview'
|
||||
}
|
||||
},
|
||||
errorCode() {
|
||||
return (this.error.message === '404' || this.error.message === '403') ? parseInt(this.error.message) : 500
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -120,13 +81,14 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('scroll', this.scroll)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('scroll', this.scroll)
|
||||
},
|
||||
destroyed () {
|
||||
if (this.$store.state.showShell) {
|
||||
this.$store.commit('toggleShell')
|
||||
}
|
||||
this.$store.commit('updateRequest', {})
|
||||
},
|
||||
methods: {
|
||||
@@ -171,74 +133,11 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
// If we're on a listing, unselect all
|
||||
// files and folders.
|
||||
if (this.isListing) {
|
||||
this.$store.commit('resetSelected')
|
||||
}
|
||||
}
|
||||
|
||||
// Del!
|
||||
if (event.keyCode === 46) {
|
||||
if (this.isEditor ||
|
||||
!this.isFiles ||
|
||||
this.loading ||
|
||||
!this.user.perm.delete ||
|
||||
(this.isListing && this.selectedCount === 0) ||
|
||||
this.$store.state.show != null) return
|
||||
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault()
|
||||
this.$store.commit('showHover', 'help')
|
||||
}
|
||||
|
||||
// F2!
|
||||
if (event.keyCode === 113) {
|
||||
if (this.isEditor ||
|
||||
!this.isFiles ||
|
||||
this.loading ||
|
||||
!this.user.perm.rename ||
|
||||
(this.isListing && this.selectedCount === 0) ||
|
||||
(this.isListing && this.selectedCount > 1)) return
|
||||
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
|
||||
// CTRL + S
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (this.isEditor) return
|
||||
|
||||
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.req.kind !== 'editor') {
|
||||
document.getElementById('download-button').click()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll () {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.user.viewMode === 'mosaic') return
|
||||
|
||||
let top = 112 - window.scrollY
|
||||
|
||||
if (top < 64) {
|
||||
top = 64
|
||||
}
|
||||
|
||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||
},
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openSearch () {
|
||||
this.$store.commit('showHover', 'search')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<div id="progress">
|
||||
<div v-bind:style="{ width: this.progress + '%' }"></div>
|
||||
</div>
|
||||
<site-header></site-header>
|
||||
<sidebar></sidebar>
|
||||
<main>
|
||||
<router-view></router-view>
|
||||
@@ -17,7 +16,6 @@
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
import Prompts from '@/components/prompts/Prompts'
|
||||
import SiteHeader from '@/components/Header'
|
||||
import Shell from '@/components/Shell'
|
||||
import { enableExec } from '@/utils/constants'
|
||||
|
||||
@@ -25,7 +23,6 @@ export default {
|
||||
name: 'layout',
|
||||
components: {
|
||||
Sidebar,
|
||||
SiteHeader,
|
||||
Prompts,
|
||||
Shell
|
||||
},
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<ul id="nav">
|
||||
<li :class="{ active: $route.path === '/settings/profile' }"><router-link to="/settings/profile">{{ $t('settings.profileSettings') }}</router-link></li>
|
||||
<li :class="{ active: $route.path === '/settings/shares' }"><router-link to="/settings/shares">{{ $t('settings.shareManagement') }}</router-link></li>
|
||||
<li v-if="user.perm.admin" :class="{ active: $route.path === '/settings/global' }"><router-link to="/settings/global">{{ $t('settings.globalSettings') }}</router-link></li>
|
||||
<li v-if="user.perm.admin" :class="{ active: $route.path === '/settings/users' }"><router-link to="/settings/users">{{ $t('settings.userManagement') }}</router-link></li>
|
||||
</ul>
|
||||
<header-bar showMenu showLogo />
|
||||
|
||||
<div id="nav">
|
||||
<div class="wrapper">
|
||||
<ul>
|
||||
<router-link to="/settings/profile"><li :class="{ active: $route.path === '/settings/profile' }">{{ $t('settings.profileSettings') }}</li></router-link>
|
||||
<router-link to="/settings/shares"><li :class="{ active: $route.path === '/settings/shares' }">{{ $t('settings.shareManagement') }}</li></router-link>
|
||||
<router-link to="/settings/global"><li :class="{ active: $route.path === '/settings/global' }" v-if="user.perm.admin">{{ $t('settings.globalSettings') }}</li></router-link>
|
||||
<router-link to="/settings/users"><li :class="{ active: $route.path === '/settings/users' || $route.name === 'User' }" v-if="user.perm.admin">{{ $t('settings.userManagement') }}</li></router-link>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
@@ -14,8 +20,15 @@
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
computed: mapState([ 'user' ])
|
||||
components: {
|
||||
HeaderBar
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,135 +1,139 @@
|
||||
<template>
|
||||
<div v-if="!loading">
|
||||
<div id="breadcrumbs">
|
||||
<router-link :to="'/share/' + hash" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</router-link>
|
||||
<div>
|
||||
<header-bar showMenu showLogo>
|
||||
<title />
|
||||
|
||||
<span v-for="(link, index) in breadcrumbs" :key="index">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<router-link :to="link.url">{{ link.name }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div class="share">
|
||||
<div class="share__box share__box__info">
|
||||
<div class="share__box__header">
|
||||
{{ req.isDir ? $t('download.downloadFolder') : $t('download.downloadFile') }}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center share__box__icon">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.displayName') }}</strong> {{ req.name }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.size') }}:</strong> {{ humanSize }}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<a target="_blank" :href="link" class="button button--flat">{{ $t('buttons.download') }}</a>
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="req.isDir && req.items.length > 0" class="share__box share__box__items">
|
||||
<div class="share__box__header" v-if="req.isDir">
|
||||
{{ $t('files.files') }}
|
||||
</div>
|
||||
<div id="listing" class="list">
|
||||
<item v-for="(item) in req.items.slice(0, this.showLimit)"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
<div v-if="req.items.length > showLimit" class="item">
|
||||
<div>
|
||||
<p class="name"> + {{ req.items.length - showLimit }} </p>
|
||||
<action v-if="selectedCount" icon="file_download" :label="$t('buttons.download')" @action="download" :counter="selectedCount" />
|
||||
<action icon="check_circle" :label="$t('buttons.selectMultiple')" @action="toggleMultipleSelection" />
|
||||
</header-bar>
|
||||
|
||||
<breadcrumbs :base="'/share/' + hash" />
|
||||
|
||||
<div v-if="!loading">
|
||||
<div class="share">
|
||||
<div class="share__box share__box__info">
|
||||
<div class="share__box__header">
|
||||
{{ req.isDir ? $t('download.downloadFolder') : $t('download.downloadFile') }}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center share__box__icon">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.displayName') }}</strong> {{ req.name }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}
|
||||
</div>
|
||||
<div class="share__box__element">
|
||||
<strong>{{ $t('prompts.size') }}:</strong> {{ humanSize }}
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<a target="_blank" :href="link" class="button button--flat">{{ $t('buttons.download') }}</a>
|
||||
</div>
|
||||
<div class="share__box__element share__box__center">
|
||||
<qrcode-vue :value="link" size="200" level="M"></qrcode-vue>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="req.isDir && req.items.length > 0" class="share__box share__box__items">
|
||||
<div class="share__box__header" v-if="req.isDir">
|
||||
{{ $t('files.files') }}
|
||||
</div>
|
||||
<div id="listing" class="list">
|
||||
<item v-for="(item) in req.items.slice(0, this.showLimit)"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size"
|
||||
readOnly>
|
||||
</item>
|
||||
<div v-if="req.items.length > showLimit" class="item">
|
||||
<div>
|
||||
<p class="name"> + {{ req.items.length - showLimit }} </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
|
||||
<i class="material-icons">clear</i>
|
||||
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
|
||||
<i class="material-icons">clear</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="req.isDir && req.items.length === 0" class="share__box share__box__items">
|
||||
<h2 class="message">
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
</h2>
|
||||
<div v-else-if="req.isDir && req.items.length === 0" class="share__box share__box__items">
|
||||
<h2 class="message">
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="error">
|
||||
<not-found v-if="error.message === '404'"></not-found>
|
||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||
<div v-else-if="error.message === '401'">
|
||||
<div class="card floating" id="password">
|
||||
<div v-if="attemptedPasswordLogin" class="share__wrong__password">{{ $t('login.wrongCredentials') }}</div>
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('login.password') }}</h2>
|
||||
</div>
|
||||
<div v-if="error">
|
||||
<div v-if="error.message === '401'">
|
||||
<div class="card floating" id="password">
|
||||
<div v-if="attemptedPasswordLogin" class="share__wrong__password">{{ $t('login.wrongCredentials') }}</div>
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('login.password') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<input v-focus type="password" :placeholder="$t('login.password')" v-model="password" @keyup.enter="fetchData">
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button class="button button--flat"
|
||||
@click="fetchData"
|
||||
:aria-label="$t('buttons.submit')"
|
||||
:title="$t('buttons.submit')">{{ $t('buttons.submit') }}</button>
|
||||
<div class="card-content">
|
||||
<input v-focus type="password" :placeholder="$t('login.password')" v-model="password" @keyup.enter="fetchData">
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button class="button button--flat"
|
||||
@click="fetchData"
|
||||
:aria-label="$t('buttons.submit')"
|
||||
:title="$t('buttons.submit')">{{ $t('buttons.submit') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<errors v-else :errorCode="errorCode" />
|
||||
</div>
|
||||
<internal-error v-else></internal-error>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapMutations, mapGetters} from 'vuex';
|
||||
import { share as api } from '@/api'
|
||||
import { pub as api } from '@/api'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Action from '@/components/header/Action'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
import Errors from '@/views/Errors'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import Item from "@/components/files/ListingItem"
|
||||
import Forbidden from './errors/403'
|
||||
import NotFound from './errors/404'
|
||||
import InternalError from './errors/500'
|
||||
|
||||
export default {
|
||||
name: 'share',
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
Breadcrumbs,
|
||||
Item,
|
||||
Forbidden,
|
||||
NotFound,
|
||||
InternalError,
|
||||
QrcodeVue
|
||||
QrcodeVue,
|
||||
Errors
|
||||
},
|
||||
data: () => ({
|
||||
error: null,
|
||||
path: '',
|
||||
showLimit: 500,
|
||||
password: '',
|
||||
attemptedPasswordLogin: false
|
||||
attemptedPasswordLogin: false,
|
||||
hash: null,
|
||||
token: null
|
||||
}),
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
created: async function () {
|
||||
const hash = this.$route.params.pathMatch.split('/')[0]
|
||||
this.setHash(hash)
|
||||
this.hash = hash
|
||||
await this.fetchData()
|
||||
},
|
||||
mounted () {
|
||||
@@ -139,8 +143,8 @@ export default {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
},
|
||||
computed: {
|
||||
...mapState(['hash', 'req', 'loading', 'multiple']),
|
||||
...mapGetters(['selectedCount']),
|
||||
...mapState(['req', 'loading', 'multiple', 'selected']),
|
||||
...mapGetters(['selectedCount', 'selectedCount']),
|
||||
icon: function () {
|
||||
if (this.req.isDir) return 'folder'
|
||||
if (this.req.type === 'image') return 'insert_photo'
|
||||
@@ -153,10 +157,9 @@ export default {
|
||||
if (this.token !== ''){
|
||||
queryArg = `?token=${this.token}`
|
||||
}
|
||||
return `${baseURL}/api/public/dl/${this.hash}${this.path}${queryArg}`
|
||||
},
|
||||
fullLink: function () {
|
||||
return window.location.origin + this.link
|
||||
|
||||
const path = this.$route.path.split('/').splice(2).join('/')
|
||||
return `${baseURL}/api/public/dl/${path}${queryArg}`
|
||||
},
|
||||
humanSize: function () {
|
||||
if (this.req.isDir) {
|
||||
@@ -168,40 +171,12 @@ export default {
|
||||
humanTime: function () {
|
||||
return moment(this.req.modified).fromNow()
|
||||
},
|
||||
breadcrumbs () {
|
||||
let parts = this.path.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/share/' + this.hash + '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
}
|
||||
}
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
errorCode() {
|
||||
return (this.error.message === '404' || this.error.message === '403') ? parseInt(this.error.message) : 500
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'setHash', 'resetSelected', 'updateRequest', 'setLoading' ]),
|
||||
...mapMutations([ 'resetSelected', 'updateRequest', 'setLoading' ]),
|
||||
base64: function (name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)))
|
||||
},
|
||||
@@ -216,19 +191,19 @@ export default {
|
||||
this.setLoading(true)
|
||||
this.error = null
|
||||
|
||||
if (this.password !== ''){
|
||||
this.attemptedPasswordLogin = true
|
||||
}
|
||||
|
||||
let url = this.$route.path
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
try {
|
||||
if (this.password !== ''){
|
||||
this.attemptedPasswordLogin = true
|
||||
}
|
||||
let file = await api.getHash(encodeURIComponent(this.$route.params.pathMatch), this.password)
|
||||
this.path = file.path
|
||||
let file = await api.fetch(url, this.password)
|
||||
|
||||
this.token = file.token || ''
|
||||
this.$store.commit('setToken', this.token)
|
||||
if (file.isDir) file.items = file.items.map((item, index) => {
|
||||
item.index = index
|
||||
item.url = `/share/${this.hash}${this.path}/${encodeURIComponent(item.name)}`
|
||||
return item
|
||||
})
|
||||
|
||||
this.updateRequest(file)
|
||||
this.setLoading(false)
|
||||
} catch (e) {
|
||||
@@ -247,6 +222,27 @@ export default {
|
||||
},
|
||||
toggleMultipleSelection () {
|
||||
this.$store.commit('multiple', !this.multiple)
|
||||
},
|
||||
download () {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.hash, this.token, this.req.items[this.selected[0]].path)
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'download',
|
||||
confirm: (format) => {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
let files = []
|
||||
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].path)
|
||||
}
|
||||
|
||||
api.download(format, this.hash, this.token, ...files)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">error</i>
|
||||
<span>{{ $t('errors.forbidden') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'forbidden'}
|
||||
</script>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">gps_off</i>
|
||||
<span>{{ $t('errors.notFound') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'not-found'}
|
||||
</script>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">error_outline</i>
|
||||
<span>{{ $t('errors.internal') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'internal-error'}
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
<template>
|
||||
<div id="editor-container">
|
||||
<div class="bar">
|
||||
<button @click="back" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close" class="action">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
<header-bar>
|
||||
<action icon="close" :label="$t('buttons.close')" @action="close()" />
|
||||
<title>{{ req.name }}</title>
|
||||
|
||||
<div class="title">
|
||||
<span>{{ req.name }}</span>
|
||||
</div>
|
||||
<template #actions>
|
||||
<action id="save-button" icon="save" :label="$t('buttons.save')" @action="save()" />
|
||||
</template>
|
||||
</header-bar>
|
||||
|
||||
<button @click="save" v-show="user.perm.modify" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" id="save-button" class="action">
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="breadcrumbs">
|
||||
<span><i class="material-icons">home</i></span>
|
||||
|
||||
<span v-for="(link, index) in breadcrumbs" :key="index">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<span>{{ link.name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<breadcrumbs base="/files" noLink />
|
||||
|
||||
<form id="editor"></form>
|
||||
</div>
|
||||
@@ -30,16 +18,25 @@
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import { theme } from '@/utils/constants'
|
||||
import buttons from '@/utils/buttons'
|
||||
import url from '@/utils/url'
|
||||
|
||||
import ace from 'ace-builds/src-min-noconflict/ace.js'
|
||||
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js'
|
||||
import 'ace-builds/webpack-resolver'
|
||||
import { theme } from '@/utils/constants'
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Action from '@/components/header/Action'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
export default {
|
||||
name: 'editor',
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
Breadcrumbs
|
||||
},
|
||||
data: function () {
|
||||
return {}
|
||||
},
|
||||
@@ -82,7 +79,7 @@ export default {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
this.editor.destroy();
|
||||
},
|
||||
mounted: function () {
|
||||
mounted: function () {
|
||||
const fileContent = this.req.content || '';
|
||||
|
||||
this.editor = ace.edit('editor', {
|
||||
@@ -126,6 +123,12 @@ export default {
|
||||
buttons.done(button)
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
close () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
|
||||
let uri = url.removeLastDir(this.$route.path) + '/'
|
||||
this.$router.push({ path: uri })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +1,169 @@
|
||||
<template>
|
||||
<div v-if="(req.numDirs + req.numFiles) == 0">
|
||||
<h2 class="message">
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
</h2>
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
|
||||
</div>
|
||||
<div v-else id="listing"
|
||||
:class="user.viewMode">
|
||||
<div>
|
||||
<div class="item header">
|
||||
<div></div>
|
||||
<div>
|
||||
<p :class="{ active: nameSorted }" class="name"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('name')"
|
||||
:title="$t('files.sortByName')"
|
||||
:aria-label="$t('files.sortByName')">
|
||||
<span>{{ $t('files.name') }}</span>
|
||||
<i class="material-icons">{{ nameIcon }}</i>
|
||||
</p>
|
||||
<div>
|
||||
<header-bar showMenu showLogo>
|
||||
<search /> <title />
|
||||
<action class="search-button" icon="search" :label="$t('buttons.search')" @action="openSearch()" />
|
||||
|
||||
<p :class="{ active: sizeSorted }" class="size"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('size')"
|
||||
:title="$t('files.sortBySize')"
|
||||
:aria-label="$t('files.sortBySize')">
|
||||
<span>{{ $t('files.size') }}</span>
|
||||
<i class="material-icons">{{ sizeIcon }}</i>
|
||||
</p>
|
||||
<p :class="{ active: modifiedSorted }" class="modified"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('modified')"
|
||||
:title="$t('files.sortByLastModified')"
|
||||
:aria-label="$t('files.sortByLastModified')">
|
||||
<span>{{ $t('files.lastModified') }}</span>
|
||||
<i class="material-icons">{{ modifiedIcon }}</i>
|
||||
</p>
|
||||
<template #actions>
|
||||
<template v-if="!isMobile">
|
||||
<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" />
|
||||
</template>
|
||||
|
||||
<action v-if="headerButtons.shell" icon="code" :label="$t('buttons.shell')" @action="$store.commit('toggleShell')" />
|
||||
<action :icon="user.viewMode === 'mosaic' ? 'view_list' : 'view_module'" :label="$t('buttons.switchView')" @action="switchView" />
|
||||
<action icon="file_download" :label="$t('buttons.download')" @action="download" :counter="selectedCount" />
|
||||
<action icon="file_upload" :label="$t('buttons.upload')" @action="upload" />
|
||||
<action icon="info" :label="$t('buttons.info')" show="info" />
|
||||
<action icon="check_circle" :label="$t('buttons.selectMultiple')" @action="toggleMultipleSelection" />
|
||||
</template>
|
||||
</header-bar>
|
||||
|
||||
<div v-if="isMobile" id="file-selection">
|
||||
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
|
||||
<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 v-if="$store.state.loading">
|
||||
<h2 class="message">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="(req.numDirs + req.numFiles) == 0">
|
||||
<h2 class="message">
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
</h2>
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
|
||||
</div>
|
||||
<div v-else id="listing"
|
||||
:class="user.viewMode">
|
||||
<div>
|
||||
<div class="item header">
|
||||
<div></div>
|
||||
<div>
|
||||
<p :class="{ active: nameSorted }" class="name"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('name')"
|
||||
:title="$t('files.sortByName')"
|
||||
:aria-label="$t('files.sortByName')">
|
||||
<span>{{ $t('files.name') }}</span>
|
||||
<i class="material-icons">{{ nameIcon }}</i>
|
||||
</p>
|
||||
|
||||
<p :class="{ active: sizeSorted }" class="size"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('size')"
|
||||
:title="$t('files.sortBySize')"
|
||||
:aria-label="$t('files.sortBySize')">
|
||||
<span>{{ $t('files.size') }}</span>
|
||||
<i class="material-icons">{{ sizeIcon }}</i>
|
||||
</p>
|
||||
<p :class="{ active: modifiedSorted }" class="modified"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('modified')"
|
||||
:title="$t('files.sortByLastModified')"
|
||||
:aria-label="$t('files.sortByLastModified')">
|
||||
<span>{{ $t('files.lastModified') }}</span>
|
||||
<i class="material-icons">{{ modifiedIcon }}</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
|
||||
<div v-if="req.numDirs > 0">
|
||||
<item v-for="(item) in dirs"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
|
||||
<div v-if="req.numFiles > 0">
|
||||
<item v-for="(item) in files"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
</div>
|
||||
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
|
||||
|
||||
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
|
||||
<i class="material-icons">clear</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
|
||||
<div v-if="req.numDirs > 0">
|
||||
<item v-for="(item) in dirs"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
|
||||
<div v-if="req.numFiles > 0">
|
||||
<item v-for="(item) in files"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
</div>
|
||||
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
<input style="display:none" type="file" id="upload-folder-input" @change="uploadInput($event)" webkitdirectory multiple>
|
||||
|
||||
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
|
||||
<i class="material-icons">clear</i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import Item from './ListingItem'
|
||||
import css from '@/utils/css'
|
||||
import { mapState, mapGetters, mapMutations } from 'vuex'
|
||||
import { users, files as api } from '@/api'
|
||||
import { enableExec } from '@/utils/constants'
|
||||
import * as upload from '@/utils/upload'
|
||||
import css from '@/utils/css'
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Action from '@/components/header/Action'
|
||||
import Search from '@/components/Search'
|
||||
import Item from '@/components/files/ListingItem'
|
||||
|
||||
export default {
|
||||
name: 'listing',
|
||||
components: { Item },
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
Search,
|
||||
Item
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
showLimit: 50,
|
||||
dragCounter: 0
|
||||
dragCounter: 0,
|
||||
width: window.innerWidth
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['req', 'selected', 'user', 'show']),
|
||||
...mapState([
|
||||
'req',
|
||||
'selected',
|
||||
'user',
|
||||
'show',
|
||||
'multiple',
|
||||
'selected'
|
||||
]),
|
||||
...mapGetters([
|
||||
'selectedCount'
|
||||
]),
|
||||
nameSorted () {
|
||||
return (this.req.sorting.by === 'name')
|
||||
},
|
||||
@@ -159,6 +220,21 @@ export default {
|
||||
}
|
||||
|
||||
return 'arrow_upward'
|
||||
},
|
||||
headerButtons() {
|
||||
return {
|
||||
upload: this.user.perm.create,
|
||||
download: this.user.perm.download,
|
||||
shell: this.user.perm.execute && enableExec,
|
||||
delete: this.selectedCount > 0 && this.user.perm.delete,
|
||||
rename: this.selectedCount === 1 && this.user.perm.rename,
|
||||
share: this.selectedCount === 1 && this.user.perm.share,
|
||||
move: this.selectedCount > 0 && this.user.perm.rename,
|
||||
copy: this.selectedCount > 0 && this.user.perm.create,
|
||||
}
|
||||
},
|
||||
isMobile () {
|
||||
return this.width <= 736
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
@@ -169,6 +245,7 @@ export default {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('resize', this.resizeEvent)
|
||||
window.addEventListener('scroll', this.scrollEvent)
|
||||
window.addEventListener('resize', this.windowsResize)
|
||||
document.addEventListener('dragover', this.preventDefault)
|
||||
document.addEventListener('dragenter', this.dragEnter)
|
||||
document.addEventListener('dragleave', this.dragLeave)
|
||||
@@ -179,6 +256,7 @@ export default {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('resize', this.resizeEvent)
|
||||
window.removeEventListener('scroll', this.scrollEvent)
|
||||
window.removeEventListener('resize', this.windowsResize)
|
||||
document.removeEventListener('dragover', this.preventDefault)
|
||||
document.removeEventListener('dragenter', this.dragEnter)
|
||||
document.removeEventListener('dragleave', this.dragLeave)
|
||||
@@ -190,10 +268,34 @@ export default {
|
||||
return window.btoa(unescape(encodeURIComponent(name)))
|
||||
},
|
||||
keyEvent (event) {
|
||||
// No prompts are shown
|
||||
if (this.show !== null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
// Reset files selection.
|
||||
this.$store.commit('resetSelected')
|
||||
}
|
||||
|
||||
// Del!
|
||||
if (event.keyCode === 46) {
|
||||
if (!this.user.perm.delete || this.selectedCount == 0) return
|
||||
|
||||
// Show delete prompt.
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
|
||||
// F2!
|
||||
if (event.keyCode === 113) {
|
||||
if (!this.user.perm.rename || this.selectedCount !== 1) return
|
||||
|
||||
// Show rename prompt.
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
|
||||
// Ctrl is pressed
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
return
|
||||
}
|
||||
@@ -207,7 +309,7 @@ export default {
|
||||
break
|
||||
case 'c':
|
||||
case 'x':
|
||||
this.copyCut(event, key)
|
||||
this.copyCut(event, key)
|
||||
break
|
||||
case 'v':
|
||||
this.paste(event)
|
||||
@@ -225,6 +327,10 @@ export default {
|
||||
}
|
||||
}
|
||||
break
|
||||
case 's':
|
||||
event.preventDefault()
|
||||
document.getElementById('download-button').click()
|
||||
break
|
||||
}
|
||||
},
|
||||
preventDefault (event) {
|
||||
@@ -458,6 +564,63 @@ export default {
|
||||
}
|
||||
|
||||
this.$store.commit('setReload', true)
|
||||
},
|
||||
openSearch () {
|
||||
this.$store.commit('showHover', 'search')
|
||||
},
|
||||
toggleMultipleSelection () {
|
||||
this.$store.commit('multiple', !this.multiple)
|
||||
this.$store.commit('closeHovers')
|
||||
},
|
||||
windowsResize () {
|
||||
this.width = window.innerWidth
|
||||
},
|
||||
download() {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.req.items[this.selected[0]].url)
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'download',
|
||||
confirm: (format) => {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
let files = []
|
||||
|
||||
if (this.selectedCount > 0) {
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].url)
|
||||
}
|
||||
} else {
|
||||
files.push(this.$route.path)
|
||||
}
|
||||
|
||||
api.download(format, ...files)
|
||||
}
|
||||
})
|
||||
},
|
||||
switchView: async function () {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
const data = {
|
||||
id: this.user.id,
|
||||
viewMode: (this.user.viewMode === 'mosaic') ? 'list' : 'mosaic'
|
||||
}
|
||||
|
||||
try {
|
||||
await users.update(data, ['viewMode'])
|
||||
this.$store.commit('updateUser', data)
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
upload: function () {
|
||||
if (typeof(DataTransferItem.prototype.webkitGetAsEntry) !== 'undefined') {
|
||||
this.$store.commit('showHover', 'upload')
|
||||
} else {
|
||||
document.getElementById('upload-input').click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
<template>
|
||||
<div id="previewer">
|
||||
<div class="bar">
|
||||
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
<div id="previewer" @mousemove="toggleNavigation" @touchstart="toggleNavigation">
|
||||
<header-bar>
|
||||
<action icon="close" :label="$t('buttons.close')" @action="close()" />
|
||||
<title>{{ name }}</title>
|
||||
<action :disabled="loading" v-if="isResizeEnabled && req.type === 'image'" :icon="fullSize ? 'photo_size_select_large' : 'hd'" @action="toggleSize" />
|
||||
|
||||
<div class="title">{{ this.name }}</div>
|
||||
|
||||
<preview-size-button v-if="isResizeEnabled && this.req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button>
|
||||
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</button>
|
||||
|
||||
<div id="dropdown" :class="{ active : showMore }">
|
||||
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
|
||||
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
|
||||
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
|
||||
<info-button :disabled="loading"></info-button>
|
||||
</div>
|
||||
</div>
|
||||
<template #actions>
|
||||
<action :disabled="loading" icon="mode_edit" :label="$t('buttons.rename')" show="rename" />
|
||||
<action :disabled="loading" icon="delete" :label="$t('buttons.delete')" @action="deleteFile" id="delete-button" />
|
||||
<action :disabled="loading" icon="file_download" :label="$t('buttons.download')" @action="download" />
|
||||
<action :disabled="loading" icon="info" :label="$t('buttons.info')" show="info" />
|
||||
</template>
|
||||
</header-bar>
|
||||
|
||||
<div class="loading" v-if="loading">
|
||||
<div class="spinner">
|
||||
@@ -28,18 +21,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
|
||||
<i class="material-icons">chevron_left</i>
|
||||
</button>
|
||||
<button class="action" @click="next" v-show="hasNext" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</button>
|
||||
|
||||
<template v-if="!loading">
|
||||
<div class="preview">
|
||||
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
|
||||
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
|
||||
<video v-else-if="req.type == 'video'" :src="raw" autoplay controls>
|
||||
<audio v-else-if="req.type == 'audio'" :src="raw" controls></audio>
|
||||
<video v-else-if="req.type == 'video'" :src="raw" controls>
|
||||
<track
|
||||
kind="captions"
|
||||
v-for="(sub, index) in subtitles"
|
||||
@@ -47,31 +33,35 @@
|
||||
:src="sub"
|
||||
:label="'Subtitle ' + index" :default="index === 0">
|
||||
Sorry, your browser doesn't support embedded videos,
|
||||
but don't worry, you can <a :href="download">download it</a>
|
||||
but don't worry, you can <a :href="downloadUrl">download it</a>
|
||||
and watch it with your favorite video player!
|
||||
</video>
|
||||
<object v-else-if="req.extension.toLowerCase() == '.pdf'" class="pdf" :data="raw"></object>
|
||||
<a v-else-if="req.type == 'blob'" :href="download">
|
||||
<a v-else-if="req.type == 'blob'" :href="downloadUrl">
|
||||
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-show="showMore" @click="resetPrompts" class="overlay"></div>
|
||||
<button @click="prev" @mouseover="hoverNav = true" @mouseleave="hoverNav = false" :class="{ hidden: !hasPrevious || !showNav }" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
|
||||
<i class="material-icons">chevron_left</i>
|
||||
</button>
|
||||
<button @click="next" @mouseover="hoverNav = true" @mouseleave="hoverNav = false" :class="{ hidden: !hasNext || !showNav }" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import url from '@/utils/url'
|
||||
import { baseURL, resizePreview } from '@/utils/constants'
|
||||
import { files as api } from '@/api'
|
||||
import PreviewSizeButton from '@/components/buttons/PreviewSize'
|
||||
import InfoButton from '@/components/buttons/Info'
|
||||
import DeleteButton from '@/components/buttons/Delete'
|
||||
import RenameButton from '@/components/buttons/Rename'
|
||||
import DownloadButton from '@/components/buttons/Download'
|
||||
import ExtendedImage from './ExtendedImage'
|
||||
import { baseURL, resizePreview } from '@/utils/constants'
|
||||
import url from '@/utils/url'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
import HeaderBar from '@/components/header/HeaderBar'
|
||||
import Action from '@/components/header/Action'
|
||||
import ExtendedImage from '@/components/files/ExtendedImage'
|
||||
|
||||
const mediaTypes = [
|
||||
"image",
|
||||
@@ -83,11 +73,8 @@ const mediaTypes = [
|
||||
export default {
|
||||
name: 'preview',
|
||||
components: {
|
||||
PreviewSizeButton,
|
||||
InfoButton,
|
||||
DeleteButton,
|
||||
RenameButton,
|
||||
DownloadButton,
|
||||
HeaderBar,
|
||||
Action,
|
||||
ExtendedImage
|
||||
},
|
||||
data: function () {
|
||||
@@ -97,7 +84,10 @@ export default {
|
||||
listing: null,
|
||||
name: '',
|
||||
subtitles: [],
|
||||
fullSize: false
|
||||
fullSize: false,
|
||||
showNav: true,
|
||||
navTimeout: null,
|
||||
hoverNav: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -108,14 +98,17 @@ export default {
|
||||
hasNext () {
|
||||
return (this.nextLink !== '')
|
||||
},
|
||||
download () {
|
||||
downloadUrl () {
|
||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
||||
},
|
||||
previewUrl () {
|
||||
// reload the image when the file is replaced
|
||||
const key = Date.parse(this.req.modified)
|
||||
|
||||
if (this.req.type === 'image' && !this.fullSize) {
|
||||
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
||||
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}`
|
||||
}
|
||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}`
|
||||
},
|
||||
raw () {
|
||||
return `${this.previewUrl}&inline=true`
|
||||
@@ -130,41 +123,40 @@ export default {
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.updatePreview()
|
||||
this.toggleNavigation()
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
window.addEventListener('keydown', this.key)
|
||||
this.$store.commit('setPreviewMode', true)
|
||||
this.listing = this.oldReq.items
|
||||
this.$root.$on('preview-deleted', this.deleted)
|
||||
this.updatePreview()
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.key)
|
||||
this.$store.commit('setPreviewMode', false)
|
||||
this.$root.$off('preview-deleted', this.deleted)
|
||||
},
|
||||
methods: {
|
||||
deleted () {
|
||||
this.listing = this.listing.filter(item => item.name !== this.name)
|
||||
deleteFile () {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'delete',
|
||||
confirm: () => {
|
||||
this.listing = this.listing.filter(item => item.name !== this.name)
|
||||
|
||||
if (this.hasNext) {
|
||||
this.next()
|
||||
} else if (!this.hasPrevious && !this.hasNext) {
|
||||
this.back()
|
||||
} else {
|
||||
this.prev()
|
||||
}
|
||||
},
|
||||
back () {
|
||||
this.$store.commit('setPreviewMode', false)
|
||||
let uri = url.removeLastDir(this.$route.path) + '/'
|
||||
this.$router.push({ path: uri })
|
||||
if (this.hasNext) {
|
||||
this.next()
|
||||
} else if (!this.hasPrevious && !this.hasNext) {
|
||||
this.close()
|
||||
} else {
|
||||
this.prev()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
prev () {
|
||||
this.hoverNav = false
|
||||
this.$router.push({ path: this.previousLink })
|
||||
},
|
||||
next () {
|
||||
this.hoverNav = false
|
||||
this.$router.push({ path: this.nextLink })
|
||||
},
|
||||
key (event) {
|
||||
@@ -178,7 +170,7 @@ export default {
|
||||
} else if (event.which === 37) { // left arrow
|
||||
if (this.hasPrevious) this.prev()
|
||||
} else if (event.which === 27) { // esc
|
||||
this.back()
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
async updatePreview () {
|
||||
@@ -232,6 +224,27 @@ export default {
|
||||
},
|
||||
toggleSize () {
|
||||
this.fullSize = !this.fullSize
|
||||
},
|
||||
toggleNavigation: throttle(function() {
|
||||
this.showNav = true
|
||||
|
||||
if (this.navTimeout) {
|
||||
clearTimeout(this.navTimeout)
|
||||
}
|
||||
|
||||
this.navTimeout = setTimeout(() => {
|
||||
this.showNav = false || this.hoverNav
|
||||
this.navTimeout = null
|
||||
}, 1500);
|
||||
}, 500),
|
||||
close () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
|
||||
let uri = url.removeLastDir(this.$route.path) + '/'
|
||||
this.$router.push({ path: uri })
|
||||
},
|
||||
download() {
|
||||
api.download(null, this.$route.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +1,108 @@
|
||||
<template>
|
||||
<div class="dashboard" v-if="settings !== null">
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.globalSettings') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p><input type="checkbox" v-model="settings.signup"> {{ $t('settings.allowSignup') }}</p>
|
||||
|
||||
<p><input type="checkbox" v-model="settings.createUserDir"> {{ $t('settings.createUserDir') }}</p>
|
||||
|
||||
<h3>{{ $t('settings.rules') }}</h3>
|
||||
<p class="small">{{ $t('settings.globalRules') }}</p>
|
||||
<rules :rules.sync="settings.rules" />
|
||||
|
||||
<div v-if="isExecEnabled">
|
||||
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
||||
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
||||
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
||||
<div class="row" v-if="settings !== null">
|
||||
<div class="column">
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.globalSettings') }}</h2>
|
||||
</div>
|
||||
|
||||
<h3>{{ $t('settings.branding') }}</h3>
|
||||
<div class="card-content">
|
||||
<p><input type="checkbox" v-model="settings.signup"> {{ $t('settings.allowSignup') }}</p>
|
||||
|
||||
<i18n path="settings.brandingHelp" tag="p" class="small">
|
||||
<a class="link" target="_blank" href="https://filebrowser.org/configuration/custom-branding">{{ $t('settings.documentation') }}</a>
|
||||
</i18n>
|
||||
<p><input type="checkbox" v-model="settings.createUserDir"> {{ $t('settings.createUserDir') }}</p>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" v-model="settings.branding.disableExternal" id="branding-links" />
|
||||
{{ $t('settings.disableExternalLinks') }}
|
||||
</p>
|
||||
<h3>{{ $t('settings.rules') }}</h3>
|
||||
<p class="small">{{ $t('settings.globalRules') }}</p>
|
||||
<rules :rules.sync="settings.rules" />
|
||||
|
||||
<p>
|
||||
<label for="theme">{{ $t('settings.themes.title') }}</label>
|
||||
<themes class="input input--block" :theme.sync="settings.branding.theme" id="theme"></themes>
|
||||
</p>
|
||||
<div v-if="isExecEnabled">
|
||||
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
||||
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
||||
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<label for="branding-name">{{ $t('settings.instanceName') }}</label>
|
||||
<input class="input input--block" type="text" v-model="settings.branding.name" id="branding-name" />
|
||||
</p>
|
||||
<h3>{{ $t('settings.branding') }}</h3>
|
||||
|
||||
<p>
|
||||
<label for="branding-files">{{ $t('settings.brandingDirectoryPath') }}</label>
|
||||
<input class="input input--block" type="text" v-model="settings.branding.files" id="branding-files" />
|
||||
</p>
|
||||
<i18n path="settings.brandingHelp" tag="p" class="small">
|
||||
<a class="link" target="_blank" href="https://filebrowser.org/configuration/custom-branding">{{ $t('settings.documentation') }}</a>
|
||||
</i18n>
|
||||
|
||||
</div>
|
||||
<p>
|
||||
<input type="checkbox" v-model="settings.branding.disableExternal" id="branding-links" />
|
||||
{{ $t('settings.disableExternalLinks') }}
|
||||
</p>
|
||||
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
<label for="theme">{{ $t('settings.themes.title') }}</label>
|
||||
<themes class="input input--block" :theme.sync="settings.branding.theme" id="theme"></themes>
|
||||
</p>
|
||||
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.userDefaults') }}</h2>
|
||||
</div>
|
||||
<p>
|
||||
<label for="branding-name">{{ $t('settings.instanceName') }}</label>
|
||||
<input class="input input--block" type="text" v-model="settings.branding.name" id="branding-name" />
|
||||
</p>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="small">{{ $t('settings.defaultUserDescription') }}</p>
|
||||
<p>
|
||||
<label for="branding-files">{{ $t('settings.brandingDirectoryPath') }}</label>
|
||||
<input class="input input--block" type="text" v-model="settings.branding.files" id="branding-files" />
|
||||
</p>
|
||||
|
||||
<user-form :isNew="false" :isDefault="true" :user.sync="settings.defaults" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form v-if="isExecEnabled" class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.commandRunner') }}</h2>
|
||||
</div>
|
||||
<div class="column">
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.userDefaults') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<i18n path="settings.commandRunnerHelp" tag="p" class="small">
|
||||
<code>FILE</code>
|
||||
<code>SCOPE</code>
|
||||
<a class="link" target="_blank" href="https://filebrowser.org/configuration/command-runner">{{ $t('settings.documentation') }}</a>
|
||||
</i18n>
|
||||
<div class="card-content">
|
||||
<p class="small">{{ $t('settings.defaultUserDescription') }}</p>
|
||||
|
||||
<div v-for="command in settings.commands" :key="command.name" class="collapsible">
|
||||
<input :id="command.name" type="checkbox">
|
||||
<label :for="command.name">
|
||||
<p>{{ capitalize(command.name) }}</p>
|
||||
<i class="material-icons">arrow_drop_down</i>
|
||||
</label>
|
||||
<div class="collapse">
|
||||
<textarea class="input input--block input--textarea" v-model.trim="command.value"></textarea>
|
||||
<user-form :isNew="false" :isDefault="true" :user.sync="settings.defaults" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<form v-if="isExecEnabled" class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.commandRunner') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<i18n path="settings.commandRunnerHelp" tag="p" class="small">
|
||||
<code>FILE</code>
|
||||
<code>SCOPE</code>
|
||||
<a class="link" target="_blank" href="https://filebrowser.org/configuration/command-runner">{{ $t('settings.documentation') }}</a>
|
||||
</i18n>
|
||||
|
||||
<div v-for="command in settings.commands" :key="command.name" class="collapsible">
|
||||
<input :id="command.name" type="checkbox">
|
||||
<label :for="command.name">
|
||||
<p>{{ capitalize(command.name) }}</p>
|
||||
<i class="material-icons">arrow_drop_down</i>
|
||||
</label>
|
||||
<div class="collapse">
|
||||
<textarea class="input input--block input--textarea" v-model.trim="command.value"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<form class="card" @submit="updateSettings">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.profileSettings') }}</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<form class="card" @submit="updateSettings">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.profileSettings') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p><input type="checkbox" v-model="hideDotfiles"> {{ $t('settings.hideDotfiles') }}</p>
|
||||
<p><input type="checkbox" v-model="singleClick"> {{ $t('settings.singleClick') }}</p>
|
||||
<h3>{{ $t('settings.language') }}</h3>
|
||||
<languages class="input input--block" :locale.sync="locale"></languages>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p><input type="checkbox" v-model="hideDotfiles"> {{ $t('settings.hideDotfiles') }}</p>
|
||||
<p><input type="checkbox" v-model="singleClick"> {{ $t('settings.singleClick') }}</p>
|
||||
<h3>{{ $t('settings.language') }}</h3>
|
||||
<languages class="input input--block" :locale.sync="locale"></languages>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form class="card" v-if="!user.lockPassword" @submit="updatePassword">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.changePassword') }}</h2>
|
||||
</div>
|
||||
<div class="column">
|
||||
<form class="card" v-if="!user.lockPassword" @submit="updatePassword">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.changePassword') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password">
|
||||
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password">
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password">
|
||||
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password">
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-action">
|
||||
<input class="button button--flat" type="submit" :value="$t('buttons.update')">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,40 +1,44 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.shareManagement') }}</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.shareManagement') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content full">
|
||||
<table>
|
||||
<tr>
|
||||
<th>{{ $t('settings.path') }}</th>
|
||||
<th>{{ $t('settings.shareDuration') }}</th>
|
||||
<th v-if="user.perm.admin">{{ $t('settings.username') }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<div class="card-content full">
|
||||
<table>
|
||||
<tr>
|
||||
<th>{{ $t('settings.path') }}</th>
|
||||
<th>{{ $t('settings.shareDuration') }}</th>
|
||||
<th v-if="user.perm.admin">{{ $t('settings.username') }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="link in links" :key="link.hash">
|
||||
<td><a :href="buildLink(link.hash)" target="_blank">{{ link.path }}</a></td>
|
||||
<td>
|
||||
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
|
||||
<template v-else>{{ $t('permanent') }}</template>
|
||||
</td>
|
||||
<td v-if="user.perm.admin">{{ link.username }}</td>
|
||||
<td class="small">
|
||||
<button class="action"
|
||||
@click="deleteLink($event, link)"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<tr v-for="link in links" :key="link.hash">
|
||||
<td><a :href="buildLink(link.hash)" target="_blank">{{ link.path }}</a></td>
|
||||
<td>
|
||||
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
|
||||
<template v-else>{{ $t('permanent') }}</template>
|
||||
</td>
|
||||
<td v-if="user.perm.admin">{{ link.username }}</td>
|
||||
<td class="small">
|
||||
<button class="action"
|
||||
@click="deleteLink($event, link)"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
</td>
|
||||
<td class="small">
|
||||
<button class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -73,23 +77,23 @@ export default {
|
||||
this.clip.on('success', () => {
|
||||
this.$showSuccess(this.$t('success.linkCopied'))
|
||||
})
|
||||
this.$root.$on('share-deleted', this.deleted)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.clip.destroy()
|
||||
this.$root.$off('share-deleted', this.deleted)
|
||||
},
|
||||
methods: {
|
||||
deleteLink: async function (event, link) {
|
||||
event.preventDefault()
|
||||
this.$store.commit('setHash', {
|
||||
hash: link.hash,
|
||||
path: link.path
|
||||
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'share-delete',
|
||||
confirm: () => {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
api.remove(link.hash)
|
||||
this.links = this.links.filter(item => item.hash !== link.hash)
|
||||
}
|
||||
})
|
||||
this.$store.commit('showHover', 'share-delete')
|
||||
},
|
||||
deleted (hash) {
|
||||
this.links = this.links.filter(item => item.hash !== hash)
|
||||
},
|
||||
humanTime (time) {
|
||||
return moment(time * 1000).fromNow()
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
<template>
|
||||
<div>
|
||||
<form v-if="loaded" @submit="save" class="card">
|
||||
<div class="card-title">
|
||||
<h2 v-if="user.id === 0">{{ $t('settings.newUser') }}</h2>
|
||||
<h2 v-else>{{ $t('settings.user') }} {{ user.username }}</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<form v-if="loaded" @submit="save" class="card">
|
||||
<div class="card-title">
|
||||
<h2 v-if="user.id === 0">{{ $t('settings.newUser') }}</h2>
|
||||
<h2 v-else>{{ $t('settings.user') }} {{ user.username }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<user-form :user.sync="user" :isDefault="false" :isNew="isNew" />
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<user-form :user.sync="user" :isDefault="false" :isNew="isNew" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
v-if="!isNew"
|
||||
@click.prevent="deletePrompt"
|
||||
type="button"
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
<input
|
||||
class="button button--flat"
|
||||
type="submit"
|
||||
:value="$t('buttons.save')">
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-action">
|
||||
<button
|
||||
v-if="!isNew"
|
||||
@click.prevent="deletePrompt"
|
||||
type="button"
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
<input
|
||||
class="button button--flat"
|
||||
type="submit"
|
||||
:value="$t('buttons.save')">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
|
||||
<div class="card-content">
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.users') }}</h2>
|
||||
<router-link to="/settings/users/new"><button class="button">{{ $t('buttons.new') }}</button></router-link>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.users') }}</h2>
|
||||
<router-link to="/settings/users/new"><button class="button">{{ $t('buttons.new') }}</button></router-link>
|
||||
</div>
|
||||
|
||||
<div class="card-content full">
|
||||
<table>
|
||||
<tr>
|
||||
<th>{{ $t('settings.username') }}</th>
|
||||
<th>{{ $t('settings.admin') }}</th>
|
||||
<th>{{ $t('settings.scope') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<div class="card-content full">
|
||||
<table>
|
||||
<tr>
|
||||
<th>{{ $t('settings.username') }}</th>
|
||||
<th>{{ $t('settings.admin') }}</th>
|
||||
<th>{{ $t('settings.scope') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td>{{ user.username }}</td>
|
||||
<td><i v-if="user.perm.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td>
|
||||
<td>{{ user.scope }}</td>
|
||||
<td class="small">
|
||||
<router-link :to="'/settings/users/' + user.id"><i class="material-icons">mode_edit</i></router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td>{{ user.username }}</td>
|
||||
<td><i v-if="user.perm.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td>
|
||||
<td>{{ user.scope }}</td>
|
||||
<td class="small">
|
||||
<router-link :to="'/settings/users/' + user.id"><i class="material-icons">mode_edit</i></router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
6
go.mod
6
go.mod
@@ -1,12 +1,12 @@
|
||||
module github.com/filebrowser/filebrowser/v2
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/DataDog/zstd v1.4.0 // indirect
|
||||
github.com/GeertJohan/go.rice v1.0.0
|
||||
github.com/Sereal/Sereal v0.0.0-20190430203904-6faf9605eb56 // indirect
|
||||
github.com/asdine/storm v2.1.2+incompatible
|
||||
github.com/caddyserver/caddy v1.0.3
|
||||
github.com/daaku/go.zipexe v1.0.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
@@ -39,5 +39,3 @@ require (
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
)
|
||||
|
||||
go 1.14
|
||||
|
||||
108
go.sum
108
go.sum
@@ -1,107 +1,68 @@
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.4.0 h1:vhoV+DUHnRZdKW1i5UMjAk2G4JY8wN4ayRfYDNdEhwo=
|
||||
github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Sereal/Sereal v0.0.0-20190430203904-6faf9605eb56 h1:3trCIB5GsAOIY8NxlfMztCYIhVsW9V5sZ+brsecjaiI=
|
||||
github.com/Sereal/Sereal v0.0.0-20190430203904-6faf9605eb56/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kbKTHGrXYt/Q=
|
||||
github.com/asdine/storm v2.1.2+incompatible/go.mod h1:RarYDc9hq1UPLImuiXK3BIWPJLdIygvV3PsInK0FbVQ=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115 h1:fUjoj2bT6dG8LoEe+uNsKk8J+sLkDbQkJnB6Z1F02Bc=
|
||||
github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
||||
github.com/caddyserver/caddy v1.0.3 h1:i9gRhBgvc5ifchwWtSe7pDpsdS9+Q0Rw9oYQmYUTw1w=
|
||||
github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk=
|
||||
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/daaku/go.zipexe v1.0.1 h1:wV4zMsDOI2SZ2m7Tdz1Ps96Zrx+TzaK15VbUaGozw0M=
|
||||
github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
|
||||
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780 h1:tFh1tRc4CA31yP6qDcu+Trax5wW5GuMxvkIba07qVLY=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
|
||||
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
|
||||
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -112,70 +73,46 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw=
|
||||
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a h1:BcF8coBl0QFVhe8vAMMlD+CV8EISiu9MGKLoj6ZEyJA=
|
||||
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
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 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
|
||||
github.com/lucas-clemente/quic-clients v0.1.0 h1:/P9n0nICT/GnQJkZovtBqridjxU0ao34m7DpMts79qY=
|
||||
github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk=
|
||||
github.com/lucas-clemente/quic-go v0.10.2 h1:iQtTSZVbd44k94Lu0U16lLBIG3lrnjDvQongjPd4B/s=
|
||||
github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1 h1:PEhRT94KBTY4E0KdCYmhvDGWjSFBxc68j2M6PMRix8U=
|
||||
github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1/go.mod h1:wI697HNhDFM/vBruYM3ckbszQ2+DOIeH9qdBKMdf288=
|
||||
github.com/marusama/semaphore/v2 v2.4.1 h1:Y29DhhFMvreVgoqF9EtaSJAF9t2E7Sk7i5VW81sqB8I=
|
||||
github.com/marusama/semaphore/v2 v2.4.1/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
|
||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||
@@ -187,62 +124,42 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
|
||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
||||
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 h1:0utzB5Mn6QyMzIeOn+oD7pjKQLjJwfM9bz6TkPPdxcw=
|
||||
github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
@@ -261,7 +178,6 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
||||
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
|
||||
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -269,36 +185,24 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
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/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -310,7 +214,6 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
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=
|
||||
@@ -324,7 +227,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
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=
|
||||
@@ -346,40 +248,31 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.2.1 h1:60g8zx1BijSVSgLTzLCW9UC4/+i1Ih9jJ1DR5Tgp9vE=
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -389,5 +282,4 @@ gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
15
http/http.go
15
http/http.go
@@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -14,11 +15,17 @@ type modifyRequest struct {
|
||||
Which []string `json:"which"` // Answer to: which fields?
|
||||
}
|
||||
|
||||
func NewHandler(imgSvc ImgService, fileCache FileCache, store *storage.Storage, server *settings.Server) (http.Handler, error) {
|
||||
func NewHandler(
|
||||
imgSvc ImgService,
|
||||
fileCache FileCache,
|
||||
store *storage.Storage,
|
||||
server *settings.Server,
|
||||
assetsFs fs.FS,
|
||||
) (http.Handler, error) {
|
||||
server.Clean()
|
||||
|
||||
r := mux.NewRouter()
|
||||
index, static := getStaticHandlers(store, server)
|
||||
index, static := getStaticHandlers(store, server, assetsFs)
|
||||
|
||||
// NOTE: This fixes the issue where it would redirect if people did not put a
|
||||
// trailing slash in the end. I hate this decision since this allows some awful
|
||||
@@ -47,8 +54,8 @@ func NewHandler(imgSvc ImgService, fileCache FileCache, store *storage.Storage,
|
||||
|
||||
api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("POST")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT")
|
||||
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH")
|
||||
|
||||
api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET")
|
||||
|
||||
@@ -79,7 +79,7 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
cacheKey := previewCacheKey(file.Path, previewSize)
|
||||
cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize)
|
||||
cachedFile, ok, err := fileCache.Load(r.Context(), cacheKey)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
@@ -133,6 +133,6 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func previewCacheKey(fPath string, previewSize PreviewSize) string {
|
||||
return fPath + previewSize.String()
|
||||
func previewCacheKey(fPath string, fTime int64, previewSize PreviewSize) string {
|
||||
return fmt.Sprintf("%x%x%x", fPath, fTime, previewSize)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
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
|
||||
@@ -47,21 +47,30 @@ var withHashFile = func(fn handleFunc) handleFunc {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
if file.IsDir {
|
||||
// set fs root to the shared folder
|
||||
d.user.Fs = afero.NewBasePathFs(d.user.Fs, filepath.Dir(link.Path))
|
||||
// share base path
|
||||
basePath := link.Path
|
||||
|
||||
file, err = files.NewFileInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Checker: d,
|
||||
Token: link.Token,
|
||||
})
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
// 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.NewFileInfo(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
|
||||
|
||||
25
http/raw.go
25
http/raw.go
@@ -108,8 +108,6 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
|
||||
})
|
||||
|
||||
func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||
// Checks are always done with paths with "/" as path separator.
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
if !d.Check(path) {
|
||||
return nil
|
||||
}
|
||||
@@ -134,7 +132,7 @@ func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||
|
||||
if path != commonPath {
|
||||
filename := strings.TrimPrefix(path, commonPath)
|
||||
filename = strings.TrimPrefix(filename, "/")
|
||||
filename = strings.TrimPrefix(filename, string(filepath.Separator))
|
||||
err = ar.Write(archiver.File{
|
||||
FileInfo: archiver.FileInfo{
|
||||
FileInfo: info,
|
||||
@@ -175,20 +173,25 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
name := file.Name
|
||||
if name == "." || name == "" {
|
||||
name = "archive"
|
||||
}
|
||||
name += extension
|
||||
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
|
||||
|
||||
err = ar.Create(w)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
defer ar.Close()
|
||||
|
||||
commonDir := fileutils.CommonPrefix('/', filenames...)
|
||||
commonDir := fileutils.CommonPrefix(filepath.Separator, filenames...)
|
||||
|
||||
var name string
|
||||
if len(filenames) > 1 {
|
||||
name = "_" + filepath.Base(commonDir)
|
||||
} else {
|
||||
name = file.Name
|
||||
}
|
||||
if name == "." || name == "" {
|
||||
name = "archive"
|
||||
}
|
||||
name += extension
|
||||
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
|
||||
|
||||
for _, fname := range filenames {
|
||||
err = addFile(ar, d, fname, commonDir)
|
||||
|
||||
152
http/resource.go
152
http/resource.go
@@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -71,11 +72,9 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||
}
|
||||
|
||||
// delete thumbnails
|
||||
for _, previewSizeName := range PreviewSizeNames() {
|
||||
size, _ := ParsePreviewSize(previewSizeName)
|
||||
if err := fileCache.Delete(r.Context(), previewCacheKey(file.Path, size)); err != nil { //nolint:govet
|
||||
return errToStatus(err), err
|
||||
}
|
||||
err = delThumbs(r.Context(), fileCache, file)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
@@ -90,12 +89,59 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||
})
|
||||
}
|
||||
|
||||
var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
if !d.user.Perm.Create && r.Method == http.MethodPost {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if !d.user.Perm.Modify && r.Method == http.MethodPut {
|
||||
defer func() {
|
||||
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||
}()
|
||||
|
||||
// Directories creation on POST.
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
err := d.user.Fs.MkdirAll(r.URL.Path, 0775)
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
file, err := files.NewFileInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: r.URL.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
if err == nil {
|
||||
if r.URL.Query().Get("override") != "true" {
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
|
||||
err = delThumbs(r.Context(), fileCache, file)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
return nil
|
||||
}, "upload", r.URL.Path, "", d.user)
|
||||
|
||||
if err != nil {
|
||||
_ = d.user.Fs.RemoveAll(r.URL.Path)
|
||||
}
|
||||
|
||||
return errToStatus(err), err
|
||||
})
|
||||
}
|
||||
|
||||
var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
if !d.user.Perm.Modify || !d.Check(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
@@ -103,55 +149,18 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques
|
||||
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||
}()
|
||||
|
||||
// For directories, only allow POST for creation.
|
||||
// Only allow PUT for files.
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
if r.Method == http.MethodPut {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
err := d.user.Fs.MkdirAll(r.URL.Path, 0775)
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && r.URL.Query().Get("override") != "true" {
|
||||
if _, err := d.user.Fs.Stat(r.URL.Path); err == nil {
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
}
|
||||
|
||||
action := "upload"
|
||||
if r.Method == http.MethodPut {
|
||||
action = "save"
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
err := d.RunHook(func() error {
|
||||
dir, _ := path.Split(r.URL.Path)
|
||||
err := d.user.Fs.MkdirAll(dir, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := d.user.Fs.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Gets the info about the file.
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
return nil
|
||||
}, action, r.URL.Path, "", d.user)
|
||||
}, "save", r.URL.Path, "", d.user)
|
||||
|
||||
if err != nil {
|
||||
_ = d.user.Fs.RemoveAll(r.URL.Path)
|
||||
@@ -165,6 +174,9 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
|
||||
dst := r.URL.Query().Get("destination")
|
||||
action := r.URL.Query().Get("action")
|
||||
dst, err := url.QueryUnescape(dst)
|
||||
if !d.Check(src) || !d.Check(dst) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
@@ -242,3 +254,41 @@ func addVersionSuffix(source string, fs afero.Fs) string {
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
|
||||
dir, _ := path.Split(dst)
|
||||
err := fs.MkdirAll(dir, 0775)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gets the info about the file.
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) error {
|
||||
for _, previewSizeName := range PreviewSizeNames() {
|
||||
size, _ := ParsePreviewSize(previewSizeName)
|
||||
if err := fileCache.Delete(ctx, previewCacheKey(file.Path, file.ModTime.Unix(), size)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -91,17 +90,6 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
if body.Expires == "" {
|
||||
var err error
|
||||
s, err = d.store.Share.GetPermanent(r.URL.Path, d.user.ID)
|
||||
if err == nil {
|
||||
if _, err := w.Write([]byte(path.Join(d.server.BaseURL, "/share/", s.Hash))); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
bytes := make([]byte, 6)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -10,15 +11,13 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
rice "github.com/GeertJohan/go.rice"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/auth"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/storage"
|
||||
"github.com/filebrowser/filebrowser/v2/version"
|
||||
)
|
||||
|
||||
func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *rice.Box, file, contentType string) (int, error) {
|
||||
func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys fs.FS, file, contentType string) (int, error) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
auther, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||
@@ -79,14 +78,14 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
|
||||
|
||||
data["Json"] = string(b)
|
||||
|
||||
fileContents, err := box.String(file)
|
||||
fileContents, err := fs.ReadFile(fSys, file)
|
||||
if err != nil {
|
||||
if err == os.ErrNotExist {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(fileContents))
|
||||
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(string(fileContents)))
|
||||
err = index.Execute(w, data)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
@@ -95,17 +94,14 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func getStaticHandlers(store *storage.Storage, server *settings.Server) (index, static http.Handler) {
|
||||
box := rice.MustFindBox("../frontend/dist")
|
||||
handler := http.FileServer(box.HTTPBox())
|
||||
|
||||
func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs fs.FS) (index, static http.Handler) {
|
||||
index = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
if r.Method != http.MethodGet {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||
return handleWithStaticData(w, r, d, box, "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) {
|
||||
@@ -127,11 +123,11 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server) (index,
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(r.URL.Path, ".js") {
|
||||
handler.ServeHTTP(w, r)
|
||||
http.FileServer(http.FS(assetsFs)).ServeHTTP(w, r)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return handleWithStaticData(w, r, d, box, r.URL.Path, "application/javascript; charset=utf-8")
|
||||
return handleWithStaticData(w, r, d, assetsFs, r.URL.Path, "application/javascript; charset=utf-8")
|
||||
}, "/static/", store, server)
|
||||
|
||||
return index, static
|
||||
|
||||
@@ -102,6 +102,14 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
|
||||
|
||||
if !blocking {
|
||||
log.Printf("[INFO] Nonblocking Command: \"%s\"", strings.Join(command, " "))
|
||||
defer func() {
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
log.Printf("[INFO] Nonblocking Command \"%s\" failed: %s", strings.Join(command, " "), err)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user