mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
Start the dashboard
Former-commit-id: a47df4e6c74e91270edd752b47239c9d8efdda6a [formerly bb87afcfe7aac73b74ebd39e8cc0882ff4b915a4] [formerly d55da52558b3c3145d62bbba650094b73438b506 [formerly ba7c3d4234
]]
Former-commit-id: c851558940bb6a0b3b407b1b9507edeba38a8c2a [formerly b37550ff50fd5d6b3cb8c4500b83c3d985283abc]
Former-commit-id: 7fe8613a364d466dea132f648fdabb81d4af0235
This commit is contained in:
parent
299b58a75f
commit
c9ddf10aba
@ -6,6 +6,7 @@
|
||||
export default {
|
||||
name: 'app',
|
||||
mounted: function () {
|
||||
// Remove loading animation.
|
||||
let loading = document.getElementById('loading')
|
||||
loading.classList.add('done')
|
||||
|
||||
|
171
assets/src/components/Files.vue
Normal file
171
assets/src/components/Files.vue
Normal file
@ -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>
|
135
assets/src/components/Header.vue
Normal file
135
assets/src/components/Header.vue
Normal file
@ -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>
|
||||
<div :class="{ multiple, loading }">
|
||||
<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="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>
|
||||
|
||||
<div>
|
||||
<site-header></site-header>
|
||||
<sidebar></sidebar>
|
||||
|
||||
<main>
|
||||
<div v-if="loading">
|
||||
<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>
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
|
||||
<prompts></prompts>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from './Search'
|
||||
import Preview from './Preview'
|
||||
import Listing from './Listing'
|
||||
import Editor from './Editor'
|
||||
import Sidebar from './Sidebar'
|
||||
import Prompts from './prompts/Prompts'
|
||||
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 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)`
|
||||
}
|
||||
import SiteHeader from './Header'
|
||||
|
||||
export default {
|
||||
name: 'main',
|
||||
components: {
|
||||
Search,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor,
|
||||
Sidebar,
|
||||
InfoButton,
|
||||
DeleteButton,
|
||||
RenameButton,
|
||||
DownloadButton,
|
||||
UploadButton,
|
||||
SwitchButton,
|
||||
MoveButton,
|
||||
SiteHeader,
|
||||
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: {
|
||||
'$route': 'fetchData',
|
||||
'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
|
||||
|
||||
'$route': function () {
|
||||
// Reset selected items and multiple selection.
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
assets/src/components/Settings.vue
Normal file
11
assets/src/components/Settings.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'settings'
|
||||
}
|
||||
</script>
|
@ -25,7 +25,7 @@
|
||||
</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>
|
||||
<span>Settings</span>
|
||||
</router-link>
|
||||
|
177
assets/src/components/User.vue
Normal file
177
assets/src/components/User.vue
Normal file
@ -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>
|
11
assets/src/components/Users.vue
Normal file
11
assets/src/components/Users.vue
Normal file
@ -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;
|
||||
background-color: #f8f8f8;
|
||||
user-select: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
* {
|
||||
|
@ -2,6 +2,10 @@ import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Login from '@/components/Login'
|
||||
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'
|
||||
|
||||
Vue.use(Router)
|
||||
@ -40,11 +44,29 @@ const router = new Router({
|
||||
children: [
|
||||
{
|
||||
path: '/files/*',
|
||||
name: 'Files'
|
||||
name: 'Files',
|
||||
component: Files
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard'
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
component: Settings
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'Users',
|
||||
component: Users
|
||||
},
|
||||
{
|
||||
path: '/users/',
|
||||
redirect: {
|
||||
path: '/users'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/users/*',
|
||||
name: 'User',
|
||||
component: User
|
||||
},
|
||||
{
|
||||
path: '/*',
|
||||
|
@ -10,6 +10,7 @@ const state = {
|
||||
req: {},
|
||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||
jwt: '',
|
||||
loading: false,
|
||||
reload: false,
|
||||
selected: [],
|
||||
multiple: false,
|
||||
|
@ -16,6 +16,7 @@ const mutations = {
|
||||
state.show = 'error'
|
||||
state.showMessage = value
|
||||
},
|
||||
setLoading: (state, value) => { state.loading = value },
|
||||
setReload: (state, value) => { state.reload = value },
|
||||
setUser: (state, value) => (state.user = value),
|
||||
setJWT: (state, value) => (state.jwt = value),
|
||||
|
@ -105,7 +105,7 @@ function move (oldLink, newLink) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
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('Destination', newLink)
|
||||
|
||||
@ -190,6 +190,27 @@ function download (format, ...files) {
|
||||
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 {
|
||||
delete: rm,
|
||||
fetch,
|
||||
@ -199,5 +220,6 @@ export default {
|
||||
post,
|
||||
command,
|
||||
search,
|
||||
download
|
||||
download,
|
||||
getUser
|
||||
}
|
||||
|
@ -85,21 +85,21 @@ type User struct {
|
||||
// Rule is a dissalow/allow rule.
|
||||
type Rule struct {
|
||||
// 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 bool
|
||||
Allow bool `json:"allow"`
|
||||
|
||||
// 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 *Regexp
|
||||
Regexp *Regexp `json:"regexp"`
|
||||
}
|
||||
|
||||
// Regexp is a regular expression wrapper around native regexp.
|
||||
type Regexp struct {
|
||||
Raw string
|
||||
Raw string `json:"raw"`
|
||||
regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user