Start the dashboard
Former-commit-id: a47df4e6c74e91270edd752b47239c9d8efdda6a [formerly bb87afcfe7aac73b74ebd39e8cc0882ff4b915a4] [formerly d55da52558b3c3145d62bbba650094b73438b506 [formerly ba7c3d4234
]]
Former-commit-id: c851558940bb6a0b3b407b1b9507edeba38a8c2a [formerly b37550ff50fd5d6b3cb8c4500b83c3d985283abc]
Former-commit-id: 7fe8613a364d466dea132f648fdabb81d4af0235
pull/726/head
parent
299b58a75f
commit
c9ddf10aba
|
@ -6,6 +6,7 @@
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
// Remove loading animation.
|
||||||
let loading = document.getElementById('loading')
|
let loading = document.getElementById('loading')
|
||||||
loading.classList.add('done')
|
loading.classList.add('done')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="error">
|
||||||
|
<h2 class="message" v-if="error === 404">
|
||||||
|
<i class="material-icons">gps_off</i>
|
||||||
|
<span>This location can't be reached.</span>
|
||||||
|
</h2>
|
||||||
|
<h2 class="message" v-else-if="error === 403">
|
||||||
|
<i class="material-icons">error</i>
|
||||||
|
<span>You're not welcome here.</span>
|
||||||
|
</h2>
|
||||||
|
<h2 class="message" v-else>
|
||||||
|
<i class="material-icons">error_outline</i>
|
||||||
|
<span>Something really went wrong.</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<editor v-else-if="isEditor"></editor>
|
||||||
|
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||||
|
<preview v-else-if="isPreview"></preview>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Preview from './Preview'
|
||||||
|
import Listing from './Listing'
|
||||||
|
import Editor from './Editor'
|
||||||
|
import css from '@/utils/css'
|
||||||
|
import api from '@/utils/api'
|
||||||
|
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||||
|
|
||||||
|
function updateColumnSizes () {
|
||||||
|
let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
|
||||||
|
let items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
|
||||||
|
|
||||||
|
if (columns === 0) columns = 1
|
||||||
|
|
||||||
|
items.style.width = `calc(${100 / columns}% - 1em)`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'files',
|
||||||
|
components: {
|
||||||
|
Preview,
|
||||||
|
Listing,
|
||||||
|
Editor
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'selectedCount'
|
||||||
|
]),
|
||||||
|
...mapState([
|
||||||
|
'req',
|
||||||
|
'user',
|
||||||
|
'reload',
|
||||||
|
'multiple',
|
||||||
|
'loading'
|
||||||
|
]),
|
||||||
|
isListing () {
|
||||||
|
return this.req.kind === 'listing' && !this.loading
|
||||||
|
},
|
||||||
|
isPreview () {
|
||||||
|
return this.req.kind === 'preview' && !this.loading
|
||||||
|
},
|
||||||
|
isEditor () {
|
||||||
|
return this.req.kind === 'editor' && !this.loading
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fetchData()
|
||||||
|
console.log('created')
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route': 'fetchData',
|
||||||
|
'reload': function () {
|
||||||
|
this.$store.commit('setReload', false)
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
updateColumnSizes()
|
||||||
|
window.addEventListener('resize', updateColumnSizes)
|
||||||
|
window.addEventListener('keydown', this.keyEvent)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations([ 'setLoading' ]),
|
||||||
|
fetchData () {
|
||||||
|
// Set loading to true and reset the error.
|
||||||
|
this.setLoading(true)
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
let url = this.$route.path
|
||||||
|
if (url === '') url = '/'
|
||||||
|
if (url[0] !== '/') url = '/' + url
|
||||||
|
|
||||||
|
api.fetch(url)
|
||||||
|
.then((trueURL) => {
|
||||||
|
if (!url.endsWith('/') && trueURL.endsWith('/')) {
|
||||||
|
console.log(trueURL)
|
||||||
|
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLoading(false)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
this.error = error
|
||||||
|
this.setLoading(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
keyEvent (event) {
|
||||||
|
// Esc!
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
this.$store.commit('closeHovers')
|
||||||
|
|
||||||
|
if (this.req.kind !== 'listing') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're on a listing, unselect all files and folders.
|
||||||
|
let items = document.getElementsByClassName('item')
|
||||||
|
Array.from(items).forEach(link => {
|
||||||
|
link.setAttribute('aria-selected', false)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$store.commit('resetSelected')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Del!
|
||||||
|
if (event.keyCode === 46) {
|
||||||
|
if (this.showDeleteButton && this.req.kind !== 'editor') {
|
||||||
|
this.$store.commit('showHover', 'delete')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// F1!
|
||||||
|
if (event.keyCode === 112) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.$store.commit('showHover', 'help')
|
||||||
|
}
|
||||||
|
|
||||||
|
// F2!
|
||||||
|
if (event.keyCode === 113) {
|
||||||
|
if (this.showRenameButton) {
|
||||||
|
this.$store.commit('showHover', 'rename')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CTRL + S
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (this.req.kind !== 'editor') {
|
||||||
|
document.getElementById('download-button').click()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openSidebar () {
|
||||||
|
this.$store.commit('showHover', 'sidebar')
|
||||||
|
},
|
||||||
|
openSearch () {
|
||||||
|
this.$store.commit('showHover', 'search')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<button @click="openSidebar" aria-label="Toggle sidebar" title="Toggle sidebar" class="action">
|
||||||
|
<i class="material-icons">menu</i>
|
||||||
|
</button>
|
||||||
|
<img src="../assets/logo.svg" alt="File Manager">
|
||||||
|
<search></search>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button @click="openSearch" aria-label="Search" title="Search" class="search-button action">
|
||||||
|
<i class="material-icons">search</i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-show="showSaveButton" aria-label="Save" class="action" id="save-button">
|
||||||
|
<i class="material-icons" title="Save">save</i>
|
||||||
|
</button>
|
||||||
|
<rename-button v-show="showRenameButton"></rename-button>
|
||||||
|
<move-button v-show="showMoveButton"></move-button>
|
||||||
|
<delete-button v-show="showDeleteButton"></delete-button>
|
||||||
|
<switch-button v-show="showSwitchButton"></switch-button>
|
||||||
|
<download-button v-show="showCommonButton"></download-button>
|
||||||
|
<upload-button v-show="showUpload"></upload-button>
|
||||||
|
<info-button v-show="showCommonButton"></info-button>
|
||||||
|
|
||||||
|
<button v-show="showSelectButton" @click="$store.commit('multiple', true)" aria-label="Select multiple" class="action">
|
||||||
|
<i class="material-icons">check_circle</i>
|
||||||
|
<span>Select</span>
|
||||||
|
</button>
|
||||||
|
</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 {mapGetters, mapState} from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'main',
|
||||||
|
components: {
|
||||||
|
Search,
|
||||||
|
InfoButton,
|
||||||
|
DeleteButton,
|
||||||
|
RenameButton,
|
||||||
|
DownloadButton,
|
||||||
|
UploadButton,
|
||||||
|
SwitchButton,
|
||||||
|
MoveButton
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'selectedCount'
|
||||||
|
]),
|
||||||
|
...mapState([
|
||||||
|
'req',
|
||||||
|
'user',
|
||||||
|
'loading',
|
||||||
|
'reload',
|
||||||
|
'multiple'
|
||||||
|
]),
|
||||||
|
showSelectButton () {
|
||||||
|
return this.req.kind === 'listing' && !this.loading && this.$route.name === 'Files'
|
||||||
|
},
|
||||||
|
showSaveButton () {
|
||||||
|
return (this.req.kind === 'editor' && !this.loading) || this.$route.name === 'User'
|
||||||
|
},
|
||||||
|
showSwitchButton () {
|
||||||
|
return this.req.kind === 'listing' && this.$route.name === 'Files' && !this.loading
|
||||||
|
},
|
||||||
|
showCommonButton () {
|
||||||
|
return !(this.$route.name !== 'Files' || this.loading)
|
||||||
|
},
|
||||||
|
showUpload () {
|
||||||
|
if (this.$route.name !== 'Files' || this.loading) return false
|
||||||
|
|
||||||
|
if (this.req.kind === 'editor') return false
|
||||||
|
return this.user.allowNew
|
||||||
|
},
|
||||||
|
showDeleteButton () {
|
||||||
|
if (this.$route.name !== 'Files' || this.loading) return false
|
||||||
|
|
||||||
|
if (this.req.kind === 'listing') {
|
||||||
|
if (this.selectedCount === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.user.allowEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.user.allowEdit
|
||||||
|
},
|
||||||
|
showRenameButton () {
|
||||||
|
if (this.$route.name !== 'Files' || this.loading) return false
|
||||||
|
|
||||||
|
if (this.req.kind === 'listing') {
|
||||||
|
if (this.selectedCount === 1) {
|
||||||
|
return this.user.allowEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.user.allowEdit
|
||||||
|
},
|
||||||
|
showMoveButton () {
|
||||||
|
if (this.$route.name !== 'Files' || this.loading) return false
|
||||||
|
|
||||||
|
if (this.req.kind !== 'listing') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedCount > 0) {
|
||||||
|
return this.user.allowEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openSidebar () {
|
||||||
|
this.$store.commit('showHover', 'sidebar')
|
||||||
|
},
|
||||||
|
openSearch () {
|
||||||
|
this.$store.commit('showHover', 'search')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,273 +1,34 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ multiple, loading }">
|
|
||||||
<header>
|
|
||||||
<div>
|
<div>
|
||||||
<button @click="openSidebar" aria-label="Toggle sidebar" title="Toggle sidebar" class="action">
|
<site-header></site-header>
|
||||||
<i class="material-icons">menu</i>
|
|
||||||
</button>
|
|
||||||
<img src="../assets/logo.svg" alt="File Manager">
|
|
||||||
<search></search>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button @click="openSearch" aria-label="Search" title="Search" class="search-button action">
|
|
||||||
<i class="material-icons">search</i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button v-show="isEditor" aria-label="Save" class="action" id="save-button">
|
|
||||||
<i class="material-icons" title="Save">save</i>
|
|
||||||
</button>
|
|
||||||
<rename-button v-show="!loading && showRenameButton"></rename-button>
|
|
||||||
<move-button v-show="!loading && showMoveButton"></move-button>
|
|
||||||
<delete-button v-show="!loading && showDeleteButton"></delete-button>
|
|
||||||
<switch-button v-show="!loading && req.kind !== 'editor'"></switch-button>
|
|
||||||
<download-button></download-button>
|
|
||||||
<upload-button v-show="!loading && showUpload"></upload-button>
|
|
||||||
<info-button></info-button>
|
|
||||||
|
|
||||||
<button v-show="isListing" @click="$store.commit('multiple', true)" aria-label="Select multiple" class="action">
|
|
||||||
<i class="material-icons">check_circle</i>
|
|
||||||
<span>Select</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<sidebar></sidebar>
|
<sidebar></sidebar>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div v-if="loading">
|
<router-view></router-view>
|
||||||
<h2 class="message">
|
|
||||||
<span>Loading...</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="error">
|
|
||||||
<h2 class="message" v-if="error === 404">
|
|
||||||
<i class="material-icons">gps_off</i>
|
|
||||||
<span>This location can't be reached.</span>
|
|
||||||
</h2>
|
|
||||||
<h2 class="message" v-else-if="error === 403">
|
|
||||||
<i class="material-icons">error</i>
|
|
||||||
<span>You're not welcome here.</span>
|
|
||||||
</h2>
|
|
||||||
<h2 class="message" v-else>
|
|
||||||
<i class="material-icons">error_outline</i>
|
|
||||||
<span>Something really went wrong.</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<editor v-else-if="isEditor"></editor>
|
|
||||||
<listing v-else-if="isListing"></listing>
|
|
||||||
<preview v-else-if="isPreview"></preview>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<prompts></prompts>
|
<prompts></prompts>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Search from './Search'
|
import Search from './Search'
|
||||||
import Preview from './Preview'
|
|
||||||
import Listing from './Listing'
|
|
||||||
import Editor from './Editor'
|
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
import Prompts from './prompts/Prompts'
|
import Prompts from './prompts/Prompts'
|
||||||
import InfoButton from './buttons/Info'
|
import SiteHeader from './Header'
|
||||||
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 css from '@/utils/css'
|
|
||||||
import api from '@/utils/api'
|
|
||||||
import {mapGetters, mapState} from 'vuex'
|
|
||||||
|
|
||||||
function updateColumnSizes () {
|
|
||||||
let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
|
|
||||||
let items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
|
|
||||||
|
|
||||||
if (columns === 0) columns = 1
|
|
||||||
|
|
||||||
items.style.width = `calc(${100 / columns}% - 1em)`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'main',
|
name: 'main',
|
||||||
components: {
|
components: {
|
||||||
Search,
|
Search,
|
||||||
Preview,
|
|
||||||
Listing,
|
|
||||||
Editor,
|
|
||||||
Sidebar,
|
Sidebar,
|
||||||
InfoButton,
|
SiteHeader,
|
||||||
DeleteButton,
|
|
||||||
RenameButton,
|
|
||||||
DownloadButton,
|
|
||||||
UploadButton,
|
|
||||||
SwitchButton,
|
|
||||||
MoveButton,
|
|
||||||
Prompts
|
Prompts
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
...mapGetters([
|
|
||||||
'selectedCount'
|
|
||||||
]),
|
|
||||||
...mapState([
|
|
||||||
'req',
|
|
||||||
'user',
|
|
||||||
'reload',
|
|
||||||
'multiple'
|
|
||||||
]),
|
|
||||||
isListing () {
|
|
||||||
return this.req.kind === 'listing' && !this.loading
|
|
||||||
},
|
|
||||||
isPreview () {
|
|
||||||
return this.req.kind === 'preview' && !this.loading
|
|
||||||
},
|
|
||||||
isEditor () {
|
|
||||||
return this.req.kind === 'editor' && !this.loading
|
|
||||||
},
|
|
||||||
showUpload () {
|
|
||||||
if (this.req.kind === 'editor') return false
|
|
||||||
return this.user.allowNew
|
|
||||||
},
|
|
||||||
showDeleteButton () {
|
|
||||||
if (this.req.kind === 'listing') {
|
|
||||||
if (this.selectedCount === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.user.allowEdit
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.user.allowEdit
|
|
||||||
},
|
|
||||||
showRenameButton () {
|
|
||||||
if (this.req.kind === 'listing') {
|
|
||||||
if (this.selectedCount === 1) {
|
|
||||||
return this.user.allowEdit
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.user.allowEdit
|
|
||||||
},
|
|
||||||
showMoveButton () {
|
|
||||||
if (this.req.kind !== 'listing') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedCount > 0) {
|
|
||||||
return this.user.allowEdit
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
loading: true,
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
watch: {
|
watch: {
|
||||||
'$route': 'fetchData',
|
'$route': function () {
|
||||||
'reload': function () {
|
|
||||||
this.$store.commit('setReload', false)
|
|
||||||
this.fetchData()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
updateColumnSizes()
|
|
||||||
window.addEventListener('resize', updateColumnSizes)
|
|
||||||
window.addEventListener('keydown', this.keyEvent)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
// Set loading to true and reset the error.
|
|
||||||
this.loading = true
|
|
||||||
this.error = null
|
|
||||||
|
|
||||||
// Reset selected items and multiple selection.
|
// Reset selected items and multiple selection.
|
||||||
this.$store.commit('resetSelected')
|
this.$store.commit('resetSelected')
|
||||||
this.$store.commit('multiple', false)
|
this.$store.commit('multiple', false)
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|
||||||
let url = this.$route.path
|
|
||||||
if (url === '') url = '/'
|
|
||||||
if (url[0] !== '/') url = '/' + url
|
|
||||||
|
|
||||||
api.fetch(url)
|
|
||||||
.then((trueURL) => {
|
|
||||||
if (!url.endsWith('/') && trueURL.endsWith('/')) {
|
|
||||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
this.error = error
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
keyEvent (event) {
|
|
||||||
// Esc!
|
|
||||||
if (event.keyCode === 27) {
|
|
||||||
this.$store.commit('closeHovers')
|
|
||||||
|
|
||||||
if (this.req.kind !== 'listing') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're on a listing, unselect all files and folders.
|
|
||||||
let items = document.getElementsByClassName('item')
|
|
||||||
Array.from(items).forEach(link => {
|
|
||||||
link.setAttribute('aria-selected', false)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.$store.commit('resetSelected')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del!
|
|
||||||
if (event.keyCode === 46) {
|
|
||||||
if (this.showDeleteButton && this.req.kind !== 'editor') {
|
|
||||||
this.$store.commit('showHover', 'delete')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// F1!
|
|
||||||
if (event.keyCode === 112) {
|
|
||||||
event.preventDefault()
|
|
||||||
this.$store.commit('showHover', 'help')
|
|
||||||
}
|
|
||||||
|
|
||||||
// F2!
|
|
||||||
if (event.keyCode === 113) {
|
|
||||||
if (this.showRenameButton) {
|
|
||||||
this.$store.commit('showHover', 'rename')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CTRL + S
|
|
||||||
if (event.ctrlKey || event.metaKey) {
|
|
||||||
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (this.req.kind !== 'editor') {
|
|
||||||
document.getElementById('download-button').click()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openSidebar () {
|
|
||||||
this.$store.commit('showHover', 'sidebar')
|
|
||||||
},
|
|
||||||
openSearch () {
|
|
||||||
this.$store.commit('showHover', 'search')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'settings'
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<router-link class="action" to="/dashboard" aria-label="Settings" title="Settings">
|
<router-link class="action" to="/settings" aria-label="Settings" title="Settings">
|
||||||
<i class="material-icons">settings_applications</i>
|
<i class="material-icons">settings_applications</i>
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<h1>User</h1>
|
||||||
|
|
||||||
|
<p><label for="username">Username</label><input type="text" v-model="username" name="username"></p>
|
||||||
|
<p><label for="password">Password</label><input type="password" :disabled="passwordBlock" v-model="password" name="password"></p>
|
||||||
|
<p><label for="scope">Scope</label><input type="text" v-model="scope" name="scope"></p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>Permissions</h2>
|
||||||
|
|
||||||
|
<p class="small">You can set the user to be an administrator or choose the permissions individually.
|
||||||
|
If you select "Administrator", all of the other options will be automatically checked.
|
||||||
|
The management of users remains a privilege of an administrator.</p>
|
||||||
|
|
||||||
|
<p><input type="checkbox" v-model="admin"> Administrator</p>
|
||||||
|
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> Create new files and directories</p>
|
||||||
|
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> Edit, rename and delete files or directories.</p>
|
||||||
|
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> Execute commands</p>
|
||||||
|
|
||||||
|
<h3>Commands</h3>
|
||||||
|
|
||||||
|
<p class="small">A space separated list with the available commands for this user. Example: <i>git svn hg</i>.</p>
|
||||||
|
|
||||||
|
<input type="text" v-model="commands">
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>Rules</h2>
|
||||||
|
|
||||||
|
<p class="small">Here you can define a set of allow and disallow rules for this specific user. The blocked files won't
|
||||||
|
show up in the listings and they won't be accessible to the user. We support regex and paths relative to
|
||||||
|
the user's scope.</p>
|
||||||
|
|
||||||
|
<p class="small">Each rule goes in one different line and must start with the keyword <code>allow</code> or <code>disallow</code>.
|
||||||
|
Then you should write <code>regex</code> if you are using a regular expression and then the expression or the path.</p>
|
||||||
|
|
||||||
|
<p class="small"><strong>Examples</strong></p>
|
||||||
|
|
||||||
|
<ul class="small">
|
||||||
|
<li><code>disallow regex \\/\\..+</code> - prevents the access to any dot file (such as .git, .gitignore) in every folder.</li>
|
||||||
|
<li><code>disallow /Caddyfile</code> - blocks the access to the file named <i>Caddyfile</i> on the root of the scope</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<textarea v-model="rules"></textarea>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>CSS</h2>
|
||||||
|
|
||||||
|
<p class="small">Costum user CSS</p>
|
||||||
|
|
||||||
|
<textarea name="css"></textarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import api from '@/utils/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'user',
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
admin: false,
|
||||||
|
allowNew: false,
|
||||||
|
allowEdit: false,
|
||||||
|
allowCommands: false,
|
||||||
|
passwordBlock: true,
|
||||||
|
password: '',
|
||||||
|
username: '',
|
||||||
|
scope: '',
|
||||||
|
rules: '',
|
||||||
|
css: '',
|
||||||
|
commands: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (this.$route.path === '/users/new') return
|
||||||
|
|
||||||
|
api.getUser(this.$route.params[0]).then(user => {
|
||||||
|
this.admin = user.admin
|
||||||
|
this.allowCommands = user.allowCommands
|
||||||
|
this.allowNew = user.allowNew
|
||||||
|
this.allowEdit = user.allowEdit
|
||||||
|
this.scope = user.filesystem
|
||||||
|
this.username = user.username
|
||||||
|
this.commands = user.commands.join(' ')
|
||||||
|
this.css = user.css
|
||||||
|
|
||||||
|
for (let rule of user.rules) {
|
||||||
|
if (rule.allow) {
|
||||||
|
this.rules += 'allow '
|
||||||
|
} else {
|
||||||
|
this.rules += 'disallow '
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.regex) {
|
||||||
|
this.rules += 'regex ' + rule.regexp.raw
|
||||||
|
} else {
|
||||||
|
this.rules += rule.path
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rules += '\n'
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
admin: function () {
|
||||||
|
if (!this.admin) return
|
||||||
|
this.allowCommands = true
|
||||||
|
this.allowEdit = true
|
||||||
|
this.allowNew = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dashboard {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard textarea,
|
||||||
|
.dashboard input[type="text"],
|
||||||
|
.dashboard input[type="password"] {
|
||||||
|
padding: .5em 1em;
|
||||||
|
display: block;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
transition: .2s ease border;
|
||||||
|
color: #333;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard textarea:focus,
|
||||||
|
.dashboard textarea:hover,
|
||||||
|
.dashboard input[type="text"]:focus,
|
||||||
|
.dashboard input[type="password"]:focus,
|
||||||
|
.dashboard input[type="text"]:hover,
|
||||||
|
.dashboard input[type="password"]:hover {
|
||||||
|
border-color: #9f9f9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard textarea {
|
||||||
|
font-family: monospace;
|
||||||
|
min-height: 10em;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard p label {
|
||||||
|
margin-bottom: .2em;
|
||||||
|
display: block;
|
||||||
|
font-size: .8em
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-bottom: 2px solid rgba(181, 181, 181, 0.5);
|
||||||
|
border-top: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 0;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li code,
|
||||||
|
p code {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: .8em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Users</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'users'
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -3,6 +3,7 @@ body {
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
|
@ -2,6 +2,10 @@ import Vue from 'vue'
|
||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
import Login from '@/components/Login'
|
import Login from '@/components/Login'
|
||||||
import Main from '@/components/Main'
|
import Main from '@/components/Main'
|
||||||
|
import Files from '@/components/Files'
|
||||||
|
import Users from '@/components/Users'
|
||||||
|
import User from '@/components/User'
|
||||||
|
import Settings from '@/components/Settings'
|
||||||
import auth from '@/utils/auth.js'
|
import auth from '@/utils/auth.js'
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
|
@ -40,11 +44,29 @@ const router = new Router({
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/files/*',
|
path: '/files/*',
|
||||||
name: 'Files'
|
name: 'Files',
|
||||||
|
component: Files
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/settings',
|
||||||
name: 'Dashboard'
|
name: 'Settings',
|
||||||
|
component: Settings
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users',
|
||||||
|
name: 'Users',
|
||||||
|
component: Users
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users/',
|
||||||
|
redirect: {
|
||||||
|
path: '/users'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users/*',
|
||||||
|
name: 'User',
|
||||||
|
component: User
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/*',
|
path: '/*',
|
||||||
|
|
|
@ -10,6 +10,7 @@ const state = {
|
||||||
req: {},
|
req: {},
|
||||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||||
jwt: '',
|
jwt: '',
|
||||||
|
loading: false,
|
||||||
reload: false,
|
reload: false,
|
||||||
selected: [],
|
selected: [],
|
||||||
multiple: false,
|
multiple: false,
|
||||||
|
|
|
@ -16,6 +16,7 @@ const mutations = {
|
||||||
state.show = 'error'
|
state.show = 'error'
|
||||||
state.showMessage = value
|
state.showMessage = value
|
||||||
},
|
},
|
||||||
|
setLoading: (state, value) => { state.loading = value },
|
||||||
setReload: (state, value) => { state.reload = value },
|
setReload: (state, value) => { state.reload = value },
|
||||||
setUser: (state, value) => (state.user = value),
|
setUser: (state, value) => (state.user = value),
|
||||||
setJWT: (state, value) => (state.jwt = value),
|
setJWT: (state, value) => (state.jwt = value),
|
||||||
|
|
|
@ -105,7 +105,7 @@ function move (oldLink, newLink) {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('POST', `${store.state.baseURL}/api/resource${oldLink}`, true)
|
request.open('PATCH', `${store.state.baseURL}/api/resource${oldLink}`, true)
|
||||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
request.setRequestHeader('Destination', newLink)
|
request.setRequestHeader('Destination', newLink)
|
||||||
|
|
||||||
|
@ -190,6 +190,27 @@ function download (format, ...files) {
|
||||||
window.open(url)
|
window.open(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUser (id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('GET', `${store.state.baseURL}/api/users/${id}`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
switch (request.status) {
|
||||||
|
case 200:
|
||||||
|
resolve(JSON.parse(request.responseText))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reject(request.responseText)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
delete: rm,
|
delete: rm,
|
||||||
fetch,
|
fetch,
|
||||||
|
@ -199,5 +220,6 @@ export default {
|
||||||
post,
|
post,
|
||||||
command,
|
command,
|
||||||
search,
|
search,
|
||||||
download
|
download,
|
||||||
|
getUser
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,21 +85,21 @@ type User struct {
|
||||||
// Rule is a dissalow/allow rule.
|
// Rule is a dissalow/allow rule.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
// Regex indicates if this rule uses Regular Expressions or not.
|
// Regex indicates if this rule uses Regular Expressions or not.
|
||||||
Regex bool
|
Regex bool `json:"regex"`
|
||||||
|
|
||||||
// Allow indicates if this is an allow rule. Set 'false' to be a disallow rule.
|
// Allow indicates if this is an allow rule. Set 'false' to be a disallow rule.
|
||||||
Allow bool
|
Allow bool `json:"allow"`
|
||||||
|
|
||||||
// Path is the corresponding URL path for this rule.
|
// Path is the corresponding URL path for this rule.
|
||||||
Path string
|
Path string `json:"path"`
|
||||||
|
|
||||||
// Regexp is the regular expression. Only use this when 'Regex' was set to true.
|
// Regexp is the regular expression. Only use this when 'Regex' was set to true.
|
||||||
Regexp *Regexp
|
Regexp *Regexp `json:"regexp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regexp is a regular expression wrapper around native regexp.
|
// Regexp is a regular expression wrapper around native regexp.
|
||||||
type Regexp struct {
|
type Regexp struct {
|
||||||
Raw string
|
Raw string `json:"raw"`
|
||||||
regexp *regexp.Regexp
|
regexp *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue