Internationalization (#183)
* update dependencies to latest version
* add mising dependencies
* Syntax updates and such
* Reorganize files and translate login to portuguese
* Add i18n to buttons
* Error translations and some bug fixes
* Add i18n to files
* i18n on prompts
* update search
* Prompts and Sidebar in
* i18n to the header
* Change to YAML
* alphabetical order
* # Add simplified Chinese language (#180)
* Add Simplified Chinese and sort by alphabet
* Add more text to translations
* API Updates
* Update zh_cn.yaml (#182)
* Api Upgrades
* Simplify api and clean zh_cn lang file
* Improve error logging
* Fix some route bugs and separate login styles
* better organization
* Fix bug on api
* Build assets Tue, Aug 1, 2017 11:32:23 AM
* Rename users path and fix bug scroll event
* Start Portuguese translation and file org
* Add more to the PT translation
* Add show
* Build assets Tue Aug 1 12:01:39 GMTST 2017
* Add locale to cofnig
* Update portuguese translation
* You can change the language :)
* :D
* Build assets Tue Aug 1 17:50:31 GMTST 2017
* Update requestContext variable names
* Remove assets
* Build assets Tue Aug 1 20:48:21 GMTST 2017
Former-commit-id: 08f373725c14990f61dbb00bea43118c496c5d32 [formerly 281e23007c79dac1e9b86424201891a99d20f73a] [formerly b1b73f42debbce06b4f36e4cf97e319789c85b9f [formerly d8bc73390c
]]
Former-commit-id: 92e99405cbf9935d1cf77b0fe70b122fca552be6 [formerly 3cd365e862f2a54ada60e226a19ac607b8d0c43b]
Former-commit-id: cf9815114ac686cdf75a6b1cba15adafe493d083
pull/726/head
parent
a5a68a8944
commit
d50bec8caa
|
@ -25,6 +25,10 @@ module.exports = {
|
|||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(yml|yaml)$/,
|
||||
loader: 'yml-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(js|vue)$/,
|
||||
loader: 'eslint-loader',
|
||||
|
|
|
@ -21,11 +21,10 @@
|
|||
<!-- Add to home screen for Windows -->
|
||||
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
||||
<meta name="msapplication-TileColor" content="#2979ff">
|
||||
|
||||
<% for (var chunk of webpack.chunks) {
|
||||
<% for (var chunk of webpack.compilation.chunks) {
|
||||
for (var file of chunk.files) {
|
||||
if (file.match(/\.(js|css)$/)) { %>
|
||||
<link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
||||
<link rel="preload" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
||||
|
||||
<!-- Plugins info -->
|
||||
<script>{{ .JavaScript }}</script>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<header>
|
||||
<div>
|
||||
<button @click="openSidebar" aria-label="Toggle sidebar" title="Toggle sidebar" class="action">
|
||||
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" 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">
|
||||
<button @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.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 v-show="showSaveButton" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" class="action" id="save-button">
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
|
||||
<div v-for="plugin in plugins" :key="plugin.name">
|
||||
|
@ -30,7 +30,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<button @click="openMore" id="more" aria-label="More" title="More" class="action">
|
||||
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</button>
|
||||
|
||||
|
@ -71,9 +71,9 @@
|
|||
<upload-button v-show="showUpload"></upload-button>
|
||||
<info-button v-show="showCommonButton"></info-button>
|
||||
|
||||
<button v-show="showSelectButton" @click="openSelect" aria-label="Select multiple" class="action">
|
||||
<button v-show="showSelectButton" @click="openSelect" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action">
|
||||
<i class="material-icons">check_circle</i>
|
||||
<span>Select</span>
|
||||
<span>{{ $t('buttons.select') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
|
@ -92,7 +92,7 @@ import SwitchButton from './buttons/SwitchView'
|
|||
import MoveButton from './buttons/Move'
|
||||
import CopyButton from './buttons/Copy'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import api from '@/utils/api'
|
||||
import * as api from '@/utils/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<select v-on:change="change" :value="selected">
|
||||
<option value="en">{{ $t('languages.en') }}</option>
|
||||
<option value="pt">{{ $t('languages.pt') }}</option>
|
||||
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'languages',
|
||||
props: [ 'selected' ],
|
||||
methods: {
|
||||
change (event) {
|
||||
this.$emit('update:selected', event.target.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,118 +0,0 @@
|
|||
<template>
|
||||
<div id="login">
|
||||
<form @submit="submit">
|
||||
<img src="../assets/logo.svg" alt="File Manager">
|
||||
<h1>File Manager</h1>
|
||||
<div v-if="wrong" class="wrong">Wrong credentials</div>
|
||||
<input type="text" v-model="username" placeholder="Username">
|
||||
<input type="password" v-model="password" placeholder="Password">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import auth from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
data: function () {
|
||||
return {
|
||||
wrong: false,
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit: function (event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
let redirect = this.$route.query.redirect
|
||||
if (redirect === '' || redirect === undefined || redirect === null) {
|
||||
redirect = '/files/'
|
||||
}
|
||||
|
||||
auth.login(this.username, this.password)
|
||||
.then(() => {
|
||||
this.$router.push({ path: redirect })
|
||||
})
|
||||
.catch(() => {
|
||||
this.wrong = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#login {
|
||||
background: #fff;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#login img {
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#login h1 {
|
||||
text-align: center;
|
||||
font-size: 2.5em;
|
||||
margin: .4em 0 .67em;
|
||||
}
|
||||
|
||||
#login form {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 16em;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#login input {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
|
||||
#login .wrong {
|
||||
background: #F44336;
|
||||
color: #fff;
|
||||
padding: .5em;
|
||||
text-align: center;
|
||||
animation: .2s opac forwards;
|
||||
}
|
||||
|
||||
@keyframes opac {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#login input[type="text"],
|
||||
#login input[type="password"] {
|
||||
padding: .5em 1em;
|
||||
border: 1px solid #e9e9e9;
|
||||
transition: .2s ease border;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#login input[type="text"]:focus,
|
||||
#login input[type="password"]:focus,
|
||||
#login input[type="text"]:hover,
|
||||
#login input[type="password"]:hover {
|
||||
border-color: #9f9f9f;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
<template>
|
||||
<div class="dashboard">
|
||||
<h1>Profile Settings</h1>
|
||||
|
||||
<ul v-if="user.admin">
|
||||
<li><router-link to="/settings/global">Go to Global Settings</router-link></li>
|
||||
</ul>
|
||||
|
||||
<form @submit="changePassword">
|
||||
<h2>Change Password</h2>
|
||||
<p><input :class="passwordClass" type="password" placeholder="Your new password" v-model="password" name="password"></p>
|
||||
<p><input :class="passwordClass" type="password" placeholder="Confirm your new password" v-model="passwordConf" name="password"></p>
|
||||
<p><input type="submit" value="Change Password"></p>
|
||||
</form>
|
||||
|
||||
<form @submit="updateCSS">
|
||||
<h2>Custom Stylesheet</h2>
|
||||
<textarea v-model="css" name="css"></textarea>
|
||||
<p><input type="submit" value="Update"></p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import api from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
data: function () {
|
||||
return {
|
||||
password: '',
|
||||
passwordConf: '',
|
||||
css: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ]),
|
||||
passwordClass () {
|
||||
if (this.password === '' && this.passwordConf === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (this.password === this.passwordConf) {
|
||||
return 'green'
|
||||
}
|
||||
|
||||
return 'red'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.css = this.user.css
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showSuccess' ]),
|
||||
changePassword (event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.password !== this.passwordConf) {
|
||||
return
|
||||
}
|
||||
|
||||
api.updatePassword(this.password).then(() => {
|
||||
this.showSuccess('Password updated!')
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
},
|
||||
updateCSS (event) {
|
||||
event.preventDefault()
|
||||
|
||||
api.updateCSS(this.css).then(() => {
|
||||
this.$store.commit('setUserCSS', this.css)
|
||||
this.$emit('css-updated')
|
||||
this.showSuccess('Styles updated!')
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
||||
<div id="input">
|
||||
<button v-if="active" class="action" @click="close">
|
||||
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
|
||||
<i class="material-icons">arrow_back</i>
|
||||
</button>
|
||||
<i v-else class="material-icons">search</i>
|
||||
|
@ -11,7 +11,7 @@
|
|||
ref="input"
|
||||
:autofocus="active"
|
||||
v-model.trim="value"
|
||||
aria-label="Write here to search"
|
||||
:aria-label="$t('search.writeToSearch')"
|
||||
:placeholder="placeholder">
|
||||
</div>
|
||||
|
||||
|
@ -78,10 +78,10 @@ export default {
|
|||
// Placeholder value.
|
||||
placeholder: function () {
|
||||
if (this.user.allowCommands && this.user.commands.length > 0) {
|
||||
return 'Search or execute a command...'
|
||||
return this.$t('search.searchOrCommand')
|
||||
}
|
||||
|
||||
return 'Search...'
|
||||
return this.$t('search.search')
|
||||
},
|
||||
// The text that is shown on the results' box while
|
||||
// there is no search result or command output to show.
|
||||
|
@ -92,16 +92,16 @@ export default {
|
|||
|
||||
if (this.value.length === 0) {
|
||||
if (this.user.allowCommands && this.user.commands.length > 0) {
|
||||
return `Search or use one of your supported commands: ${this.user.commands.join(', ')}.`
|
||||
return `${this.$t('search.searchOrSupportedCommand')} ${this.user.commands.join(', ')}.`
|
||||
}
|
||||
|
||||
return 'Type and press enter to search.'
|
||||
this.$t('search.type')
|
||||
}
|
||||
|
||||
if (!this.supported() || !this.user.allowCommands) {
|
||||
return 'Press enter to search.'
|
||||
return this.$t('search.pressToSearch')
|
||||
} else {
|
||||
return 'Press enter to execute.'
|
||||
return this.$t('search.pressToExecute')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<nav :class="{active}">
|
||||
<router-link class="action" to="/files/" aria-label="My Files" title="My Files">
|
||||
<router-link class="action" to="/files/" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')">
|
||||
<i class="material-icons">folder</i>
|
||||
<span>My Files</span>
|
||||
<span>{{ $t('sidebar.myFiles') }}</span>
|
||||
</router-link>
|
||||
|
||||
<div v-if="user.allowNew">
|
||||
<button @click="$store.commit('showHover', 'newDir')" aria-label="New directory" title="New directory" class="action">
|
||||
<button @click="$store.commit('showHover', 'newDir')" class="action" :aria-label="$t('sidebar.newFolder')" :title="$t('sidebar.newFolder')">
|
||||
<i class="material-icons">create_new_folder</i>
|
||||
<span>New folder</span>
|
||||
<span>{{ $t('sidebar.newFolder') }}</span>
|
||||
</button>
|
||||
|
||||
<button @click="$store.commit('showHover', 'newFile')" aria-label="New file" title="New file" class="action">
|
||||
<button @click="$store.commit('showHover', 'newFile')" class="action" :aria-label="$t('sidebar.newFile')" :title="$t('sidebar.newFile')">
|
||||
<i class="material-icons">note_add</i>
|
||||
<span>New file</span>
|
||||
<span>{{ $t('sidebar.newFile') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -25,21 +25,21 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<router-link class="action" to="/settings" aria-label="Settings" title="Settings">
|
||||
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')">
|
||||
<i class="material-icons">settings_applications</i>
|
||||
<span>Settings</span>
|
||||
<span>{{ $t('sidebar.settings') }}</span>
|
||||
</router-link>
|
||||
|
||||
<button @click="logout" class="action" id="logout" aria-label="Log out" title="Logout">
|
||||
<button @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>Logout</span>
|
||||
<span>{{ $t('sidebar.logout') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="credits">
|
||||
<span>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a>.</span>
|
||||
<span>{{ $t('sidebar.servedWith') }} <a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a>.</span>
|
||||
<span v-for="plugin in plugins" :key="plugin.name" v-html="plugin.credits"><br></span>
|
||||
<span><a @click="help">Help</a></span>
|
||||
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="show" aria-label="Copy" title="Copy" class="action" id="copy-button">
|
||||
<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>Copy file</span>
|
||||
<span>{{ $t('buttons.copyFile') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="show" aria-label="Delete" title="Delete" class="action" id="delete-button">
|
||||
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
|
||||
<i class="material-icons">delete</i>
|
||||
<span>Delete</span>
|
||||
<span>{{ $t('buttons.delete') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="download" aria-label="Download" title="Download" id="download-button" class="action">
|
||||
<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>Download</span>
|
||||
<span>{{ $t('buttons.download') }}</span>
|
||||
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button title="Info" aria-label="Info" class="action" @click="show">
|
||||
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
|
||||
<i class="material-icons">info</i>
|
||||
<span>Info</span>
|
||||
<span>{{ $t('buttons.info') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="show" aria-label="Move" title="Move" class="action" id="move-button">
|
||||
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
|
||||
<i class="material-icons">forward</i>
|
||||
<span>Move file</span>
|
||||
<span>{{ $t('buttons.moveFile') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="show" aria-label="Rename" title="Rename" class="action" id="rename-button">
|
||||
<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>Rename</span>
|
||||
<span>{{ $t('buttons.rename') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="change" aria-label="Switch View" title="Switch View" class="action" id="switch-view-button">
|
||||
<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>Switch view</span>
|
||||
<span>{{ $t('buttons.switchView') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button @click="upload" aria-label="Upload" title="Upload" class="action" id="upload-button">
|
||||
<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>Upload</span>
|
||||
<span>{{ $t('buttons.upload') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<form id="editor" :class="req.language">
|
||||
<div v-if="hasMetadata" id="metadata">
|
||||
<h2>Metadata</h2>
|
||||
<h2>{{ $t('files.metadata') }}</h2>
|
||||
</div>
|
||||
|
||||
<h2 v-if="hasMetadata">Body</h2>
|
||||
<h2 v-if="hasMetadata">{{ $t('files.body') }}</h2>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
@ -123,7 +123,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -2,9 +2,9 @@
|
|||
<div v-if="(req.numDirs + req.numFiles) == 0">
|
||||
<h2 class="message">
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>It feels lonely here...</span>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
</h2>
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" value="Upload" multiple>
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
</div>
|
||||
<div v-else id="listing"
|
||||
:class="req.display"
|
||||
|
@ -16,23 +16,23 @@
|
|||
<div></div>
|
||||
<div>
|
||||
<p :class="{ active: nameSorted }" class="name" @click="sort('name')">
|
||||
<span>Name</span>
|
||||
<span>{{ $t('files.name') }}</span>
|
||||
<i class="material-icons">{{ nameIcon }}</i>
|
||||
</p>
|
||||
|
||||
<p :class="{ active: sizeSorted }" class="size" @click="sort('size')">
|
||||
<span>Size</span>
|
||||
<span>{{ $t('files.size') }}</span>
|
||||
<i class="material-icons">{{ sizeIcon }}</i>
|
||||
</p>
|
||||
<p :class="{ active: modifiedSorted }" class="modified" @click="sort('modified')">
|
||||
<span>Last modified</span>
|
||||
<span>{{ $t('files.lastModified') }}</span>
|
||||
<i class="material-icons">{{ modifiedIcon }}</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numDirs > 0">Folders</h2>
|
||||
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
|
||||
<div v-if="req.numDirs > 0">
|
||||
<item v-for="(item, index) in req.items"
|
||||
v-if="item.isDir"
|
||||
|
@ -47,7 +47,7 @@
|
|||
</item>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numFiles > 0">Files</h2>
|
||||
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
|
||||
<div v-if="req.numFiles > 0">
|
||||
<item v-for="(item, index) in req.items"
|
||||
v-if="!item.isDir"
|
||||
|
@ -62,12 +62,12 @@
|
|||
</item>
|
||||
</div>
|
||||
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" value="Upload" multiple>
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
|
||||
<div v-show="$store.state.multiple" :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<p>Multiple selection enabled</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" title="Clear" aria-label="Clear" class="action">
|
||||
<i class="material-icons" title="Clear">clear</i>
|
||||
<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>
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div id="previewer">
|
||||
<div class="bar">
|
||||
<button @click="back" class="action" aria-label="Close Preview" id="close">
|
||||
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
|
||||
|
@ -11,8 +11,12 @@
|
|||
<info-button></info-button>
|
||||
</div>
|
||||
|
||||
<button class="action" @click="prev" v-show="hasPrevious"><i class="material-icons">chevron_left</i></button>
|
||||
<button class="action" @click="next" v-show="hasNext"><i class="material-icons">chevron_right</i></button>
|
||||
<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>
|
||||
|
||||
<div class="preview">
|
||||
<img v-if="req.type == 'image'" :src="raw()">
|
||||
|
@ -24,7 +28,7 @@
|
|||
</video>
|
||||
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw()"></object>
|
||||
<a v-else-if="req.type == 'blob'" :href="download()">
|
||||
<h2 class="message">Download <i class="material-icons">file_download</i></h2>
|
||||
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
|
||||
</a>
|
||||
<pre v-else >{{ req.content }}</pre>
|
||||
</div>
|
||||
|
@ -35,10 +39,10 @@
|
|||
import { mapState } from 'vuex'
|
||||
import url from '@/utils/url'
|
||||
import api from '@/utils/api'
|
||||
import InfoButton from './buttons/Info'
|
||||
import DeleteButton from './buttons/Delete'
|
||||
import RenameButton from './buttons/Rename'
|
||||
import DownloadButton from './buttons/Download'
|
||||
import InfoButton from '@/components/buttons/Info'
|
||||
import DeleteButton from '@/components/buttons/Delete'
|
||||
import RenameButton from '@/components/buttons/Rename'
|
||||
import DownloadButton from '@/components/buttons/Download'
|
||||
|
||||
export default {
|
||||
name: 'preview',
|
|
@ -1,13 +1,16 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>Copy</h3>
|
||||
<p>Choose the place to copy your files:</p>
|
||||
<h3>{{ $t('prompts.copy') }}</h3>
|
||||
<p>{{ $t('prompts.copyMessage') }}</p>
|
||||
|
||||
<file-list @update:selected="val => dest = val"></file-list>
|
||||
|
||||
<div>
|
||||
<button class="ok" @click="copy">Copy</button>
|
||||
<button class="cancel" @click="$store.commit('closeHovers')">Cancel</button>
|
||||
<button class="ok" @click="copy">{{ $t('buttons.copy') }}</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>Delete files</h3>
|
||||
<p v-show="req.kind !== 'listing'">Are you sure you want to delete this file/folder?</p>
|
||||
<p v-show="req.kind === 'listing'">Are you sure you want to delete {{ selectedCount }} file(s)?</p>
|
||||
<h3>{{ $t('prompts.deleteTitle') }}</h3>
|
||||
<p v-show="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
|
||||
<p v-show="req.kind === 'listing'">{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
|
||||
<div>
|
||||
<button @click="submit" autofocus>Delete</button>
|
||||
<button @click="closeHovers" class="cancel">Cancel</button>
|
||||
<button @click="submit" autofocus>{{ $t('buttons.delete') }}</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="prompt" id="download">
|
||||
<h3>Download files</h3>
|
||||
<p>Choose the format you want to download.</p>
|
||||
<h3>{{ $t('prompts.download') }}</h3>
|
||||
<p>{{ $t('prompts.downloadMessage') }}</p>
|
||||
|
||||
<button @click="download('zip')" autofocus>zip</button>
|
||||
<button @click="download('tar')" autofocus>tar</button>
|
||||
<button @click="download('targz')" autofocus>tar.gz</button>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div class="prompt error">
|
||||
<i class="material-icons">error_outline</i>
|
||||
<h3>Something went wrong</h3>
|
||||
<h3>{{ $t('prompts.error') }}</h3>
|
||||
<pre>{{ $store.state.showMessage }}</pre>
|
||||
<div>
|
||||
<button @click="close" autofocus>Close</button>
|
||||
<button @click="reportIssue" class="cancel">Report Issue</button>
|
||||
<button @click="close" autofocus>{{ $t('buttons.close') }}</button>
|
||||
<button @click="reportIssue" class="cancel">{{ $t('buttons.reportIssue') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:data-url="item.url">{{ item.name }}</li>
|
||||
</ul>
|
||||
|
||||
<p>Currently navigating on: <code>{{ nav }}</code>.</p>
|
||||
<p>{{ $t('prompts.currentlyNavigating') }} <code>{{ nav }}</code>.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
<template>
|
||||
<div class="prompt help">
|
||||
<h3>Help</h3>
|
||||
<h3>{{ $t('help.help') }}</h3>
|
||||
|
||||
<ul>
|
||||
<li><strong>F1</strong> - this information</li>
|
||||
<li><strong>F2</strong> - rename file</li>
|
||||
<li><strong>DEL</strong> - delete selected items</li>
|
||||
<li><strong>ESC</strong> - clear selection and/or close the prompt</li>
|
||||
<li><strong>CTRL + S</strong> - save a file or download the directory where you are</li>
|
||||
<li><strong>CTRL + Click</strong> - select multiple files or directories</li>
|
||||
<li><strong>Double click</strong> - open a file or directory</li>
|
||||
<li><strong>Click</strong> - select file or directory</li>
|
||||
</ul>
|
||||
|
||||
<p>Not available yet</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Alt + Click</strong> - select a group of files</li>
|
||||
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
|
||||
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
|
||||
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
|
||||
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
|
||||
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
|
||||
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
|
||||
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
|
||||
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
|
||||
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<button type="submit" @click="$store.commit('closeHovers')" class="ok">OK</button>
|
||||
<button type="submit" @click="$store.commit('closeHovers')" class="ok">{{ $t('buttons.ok') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>File Information</h3>
|
||||
<h3>{{ $t('prompts.fileInfo') }}</h3>
|
||||
|
||||
<p v-show="selected.length > 1">{{ selected.length }} files selected.</p>
|
||||
<p v-show="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
|
||||
|
||||
<p v-show="selected.length < 2"><strong>Display Name:</strong> {{ name() }}</p>
|
||||
<p><strong>Size:</strong> <span id="content_length"></span>{{ humanSize() }}</p>
|
||||
<p v-show="selected.length < 2"><strong>Last Modified:</strong> {{ humanTime() }}</p>
|
||||
<p v-show="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name() }}</p>
|
||||
<p><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span>{{ humanSize() }}</p>
|
||||
<p v-show="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime() }}</p>
|
||||
|
||||
<section v-show="dir() && selected.length === 0">
|
||||
<p><strong>Number of files:</strong> {{ req.numFiles }}</p>
|
||||
<p><strong>Number of directories:</strong> {{ req.numDirs }}</p>
|
||||
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
|
||||
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
|
||||
</section>
|
||||
|
||||
<section v-show="!dir()">
|
||||
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">show</a></code></p>
|
||||
<p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">show</a></code></p>
|
||||
<p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">show</a></code></p>
|
||||
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">show</a></code></p>
|
||||
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
|
||||
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<button type="submit" @click="$store.commit('closeHovers')" class="ok">OK</button>
|
||||
<button type="submit" @click="$store.commit('closeHovers')" class="ok">{{ $t('buttons.ok') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>Move</h3>
|
||||
<p>Choose new house for your file(s)/folder(s):</p>
|
||||
<h3>{{ $t('prompts.move') }}</h3>
|
||||
<p>{{ $t('prompts.moveMessage') }}</p>
|
||||
|
||||
<file-list @update:selected="val => dest = val"></file-list>
|
||||
|
||||
<div>
|
||||
<button class="ok" @click="move">Move</button>
|
||||
<button class="cancel" @click="$store.commit('closeHovers')">Cancel</button>
|
||||
<button class="ok" @click="move">{{ $t('buttons.move') }}</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>New directory</h3>
|
||||
<p>Write the name of the new directory.</p>
|
||||
<h3>{{ $t('prompts.newDir') }}</h3>
|
||||
<p>{{ $t('prompts.newDirMessage') }}</p>
|
||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<div>
|
||||
<button class="ok" @click="submit">Create</button>
|
||||
<button class="cancel" @click="$store.commit('closeHovers')">Cancel</button>
|
||||
<button class="ok" @click="submit">{{ $t('buttons.create') }}</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>New file</h3>
|
||||
<p>Write the name of the new file.</p>
|
||||
<h3>{{ $t('prompts.newFile') }}</h3>
|
||||
<p>{{ $t('prompts.newFileMessage') }}</p>
|
||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<div>
|
||||
<button class="ok" @click="submit">Create</button>
|
||||
<button class="cancel" @click="$store.commit('closeHovers')">Cancel</button>
|
||||
<button class="ok" @click="submit">{{ $t('buttons.create') }}</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -27,7 +27,10 @@
|
|||
:placeholder="input.placeholder">
|
||||
<div>
|
||||
<input type="submit" class="ok" :value="prompt.ok">
|
||||
<button class="cancel" @click.prevent="$store.commit('closeHovers')">Cancel</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<div class="prompt">
|
||||
<h3>Rename</h3>
|
||||
<p>Insert a new name for <code>{{ oldName() }}</code>:</p>
|
||||
<h3>{{ $t('prompts.rename') }}</h3>
|
||||
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
||||
|
||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<div>
|
||||
<button @click="submit" type="submit">Rename</button>
|
||||
<button @click="cancel" class="cancel">Cancel</button>
|
||||
<button @click="submit" type="submit">{{ $t('buttons.rename') }}</button>
|
||||
<button class="cancel"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<i class="material-icons">done</i>
|
||||
<h3>{{ $store.state.showMessage }}</h3>
|
||||
<div>
|
||||
<button @click="close" autofocus>OK</button>
|
||||
<button @click="close" autofocus>{{ $t('buttons.ok') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
width: 1em
|
||||
}
|
||||
|
||||
.dashboard > *:first-child {
|
||||
.dashboard > h1:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ form.dashboard > p:last-child {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dashboard select,
|
||||
.dashboard textarea,
|
||||
.dashboard input[type="text"],
|
||||
.dashboard input[type="password"] {
|
||||
|
@ -60,12 +61,18 @@ form.dashboard > p:last-child {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboard #locale,
|
||||
.dashboard #username,
|
||||
.dashboard #password,
|
||||
.dashboard #scope {
|
||||
max-width: 18em;
|
||||
}
|
||||
|
||||
.dashboard #locale {
|
||||
border: 1px solid #dddddd;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.dashboard textarea:focus,
|
||||
.dashboard textarea:hover,
|
||||
.dashboard input[type="text"]:focus,
|
||||
|
@ -118,3 +125,27 @@ p code {
|
|||
font-size: .8em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dashboard #nav {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
color: rgb(84, 110, 122);
|
||||
font-weight: 500;
|
||||
padding: 0 0 1em;
|
||||
margin: 0 0 1em;
|
||||
font-size: .8em;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dashboard #nav li {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboard #nav li:last-child {
|
||||
text-align: right
|
||||
}
|
||||
|
||||
.dashboard #nav i {
|
||||
font-size: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
#login {
|
||||
background: #fff;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#login img {
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#login h1 {
|
||||
text-align: center;
|
||||
font-size: 2.5em;
|
||||
margin: .4em 0 .67em;
|
||||
}
|
||||
|
||||
#login form {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 16em;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#login input {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
|
||||
#login .wrong {
|
||||
background: #F44336;
|
||||
color: #fff;
|
||||
padding: .5em;
|
||||
text-align: center;
|
||||
animation: .2s opac forwards;
|
||||
}
|
||||
|
||||
@keyframes opac {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#login input[type="text"],
|
||||
#login input[type="password"] {
|
||||
padding: .5em 1em;
|
||||
border: 1px solid #e9e9e9;
|
||||
transition: .2s ease border;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#login input[type="text"]:focus,
|
||||
#login input[type="password"]:focus,
|
||||
#login input[type="text"]:hover,
|
||||
#login input[type="password"]:hover {
|
||||
border-color: #9f9f9f;
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
@import "./listing.css";
|
||||
@import "./editor.css";
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* ACTION *
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
buttons:
|
||||
cancel: Cancel
|
||||
close: Close
|
||||
copy: Copy
|
||||
copyFile: Copy file
|
||||
create: Create
|
||||
delete: Delete
|
||||
download: Download
|
||||
info: Info
|
||||
more: More
|
||||
move: Move
|
||||
moveFile: Move file
|
||||
new: New
|
||||
next: Next
|
||||
ok: OK
|
||||
previous: Previous
|
||||
rename: Rename
|
||||
reportIssue: Report Issue
|
||||
save: Save
|
||||
search: Search
|
||||
select: Select
|
||||
selectMultiple: Select multiple
|
||||
switchView: Swicth view
|
||||
toggleSidebar: Toggle sidebar
|
||||
update: Update
|
||||
upload: Upload
|
||||
errors:
|
||||
forbidden: You're not welcome here.
|
||||
internal: Something really went wrong.
|
||||
notFound: This location can't be reached.
|
||||
files:
|
||||
folders: Folders
|
||||
files: Files
|
||||
body: Body
|
||||
clear: Clear
|
||||
closePreview: Close preview
|
||||
home: Home
|
||||
lastModified: Last modified
|
||||
loading: Loading...
|
||||
lonely: It feels lonely here...
|
||||
metadata: Metadata
|
||||
multipleSelectionEnabled: Multiple selection enabled
|
||||
name: Name
|
||||
size: Size
|
||||
help:
|
||||
click: select file or directory
|
||||
ctrl:
|
||||
click: select multiple files or directories
|
||||
f: opens search
|
||||
s: save a file or download the directory where you are
|
||||
del: delete selected items
|
||||
doubleClick: open a file or directory
|
||||
esc: clear selection and/or close the prompt
|
||||
f1: this information
|
||||
f2: rename file
|
||||
help: Help
|
||||
login:
|
||||
password: Password
|
||||
submit: Login
|
||||
username: Username
|
||||
wrongCredentials: Wrong credentials
|
||||
prompts:
|
||||
copy: Copy
|
||||
copyMessage: 'Choose the place to copy your files:'
|
||||
currentlyNavigating: 'Currently navigating on:'
|
||||
deleteMessageMultiple: Are you sure you want to delete {count} file(s)?
|
||||
deleteMessageSingle: Are you sure you want to delete this file/folder?
|
||||
deleteTitle: Delete files
|
||||
displayName: 'Display Name:'
|
||||
download: Download files
|
||||
downloadMessage: Choose the format you want to download.
|
||||
error: Something went wrong
|
||||
fileInfo: File information
|
||||
filesSelected: "{count} files selected."
|
||||
lastModified: Last Modified
|
||||
move: Move
|
||||
moveMessage: 'Choose new house for your file(s)/folder(s):'
|
||||
newDir: New directory
|
||||
newDirMessage: Write the name of the new directory.
|
||||
newFile: New file
|
||||
newFileMessage: Write the name of the new file.
|
||||
numberDirs: Number of directories
|
||||
numberFiles: Number of files
|
||||
rename: Rename
|
||||
renameMessage: Insert a new name for
|
||||
show: Show
|
||||
size: Size
|
||||
settings:
|
||||
admin: Admin
|
||||
administrator: Administrator
|
||||
allowCommands: Execute commands
|
||||
allowEdit: Edit, rename and delete files or directories.
|
||||
allowNew: Create new files and directories
|
||||
avoidChanges: "(leave blank to avoid changes)"
|
||||
changePassword: Change Password
|
||||
commands: Commands
|
||||
commandsHelp: >
|
||||
Here you can set commands that are executed in the named events. You
|
||||
write one command per line. If the event is related to files, such as before and
|
||||
after saving, the environment variable "file" will be available with the path
|
||||
of the file.
|
||||
commandsUpdated: Commands updated!
|
||||
customStylesheet: Custom Stylesheet
|
||||
examples: Examples
|
||||
globalSettings: Global Settings
|
||||
language: Language
|
||||
newPassword: Your new password
|
||||
newPasswordConfirm: Confirm your new password
|
||||
newUser: New User
|
||||
password: Password
|
||||
passwordUpdated: Password updated!
|
||||
permissions: Permissions
|
||||
permissionsHelp: >
|
||||
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.
|
||||
pluginsUpdated: Plugins settings updated!
|
||||
profileSettings: Profile Settings
|
||||
ruleExample1: >
|
||||
'prevents the access to any dot file (such as .git, .gitignore) in
|
||||
every folder.'
|
||||
ruleExample2: blocks the access to the file named Caddyfile on the root of the scope.
|
||||
rules: Rules
|
||||
rulesHelp1: >
|
||||
'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.'
|
||||
rulesHelp2: >
|
||||
Each rule goes in one different line and must start with the keyword
|
||||
{0} or {1}. Then you should write {2} if you are using a regular expression and
|
||||
then the expression or the path.
|
||||
scope: Scope
|
||||
settingsUpdated: Settings updated!
|
||||
user: User
|
||||
userCommands: Commands
|
||||
userCommandsHelp:
|
||||
'A space separated list with the available commands for this user.
|
||||
Example:'
|
||||
userCreated: User created!
|
||||
userDeleted: User deleted!
|
||||
userManagement: User Management
|
||||
username: Username
|
||||
users: Users
|
||||
userUpdated: User updated!
|
||||
sidebar:
|
||||
help: Help
|
||||
logout: Logout
|
||||
myFiles: My files
|
||||
newFile: New file
|
||||
newFolder: New folder
|
||||
servedWith: Served with
|
||||
settings: Settings
|
||||
search:
|
||||
writeToSearch: Write here to search
|
||||
searchOrCommand: Search or execute a command...
|
||||
searchOrSupportedCommand: 'Search or use one of your supported commands:'
|
||||
search: Search...
|
||||
type: Type and press enter to search.
|
||||
pressToSearch: Press enter to search.
|
||||
pressToExecute: Press enter to execute.
|
||||
languages:
|
||||
en: English
|
||||
pt: Portuguese
|
||||
zhCN: Chinese (Simplified)
|
|
@ -0,0 +1,19 @@
|
|||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import en from './en.yaml'
|
||||
import pt from './pt.yaml'
|
||||
import zhCN from './zh-cn.yaml'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
'en': en,
|
||||
'pt': pt,
|
||||
'zh-cn': zhCN
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
|
@ -0,0 +1,165 @@
|
|||
buttons:
|
||||
cancel: Cancelar
|
||||
close: Fechar
|
||||
copy: Copiar
|
||||
copyFile: Copiar ficheiro
|
||||
create: Criar
|
||||
delete: Eliminar
|
||||
download: Descarregar
|
||||
info: Info
|
||||
more: Mais
|
||||
move: Mover
|
||||
moveFile: Mover ficheiro
|
||||
new: Novo
|
||||
next: Próximo
|
||||
ok: OK
|
||||
previous: Anterior
|
||||
rename: Renomear
|
||||
reportIssue: Reportar Erro
|
||||
save: Guardar
|
||||
search: Pesquisar
|
||||
select: Selecionar
|
||||
selectMultiple: Selecionar múltiplos
|
||||
switchView: Alterar modo de visão
|
||||
toggleSidebar: Alternar barra lateral
|
||||
update: Atualizar
|
||||
upload: Enviar
|
||||
errors:
|
||||
forbidden: Tu não és bem-vindo aqui.
|
||||
internal: Algo correu bastante mal.
|
||||
notFound: Não conseguimos chegar a esta localização.
|
||||
files:
|
||||
folders: Pastas
|
||||
files: Ficheiros
|
||||
body: Corpo
|
||||
clear: Limpar
|
||||
closePreview: Fechar pré-visualização
|
||||
home: Início
|
||||
lastModified: Última modificação
|
||||
loading: A carregar...
|
||||
lonely: Sinto-me sozinho...
|
||||
metadata: Metadados
|
||||
multipleSelectionEnabled: Seleção múltipla ativada
|
||||
name: Nome
|
||||
size: Tamanho
|
||||
help:
|
||||
click: selecionar pasta ou ficheiro
|
||||
ctrl:
|
||||
click: selecionar várias pastas e ficheiros
|
||||
f: pesquisar
|
||||
s: guardar um ficheiro ou descarregar a pasta em que estás a navegar
|
||||
del: eliminar os ficheiros selecionados
|
||||
doubleClick: abrir pasta ou ficheiro
|
||||
esc: limpar seleção e/ou fechar menu
|
||||
f1: esta informação
|
||||
f2: renomear ficheiro
|
||||
help: Ajuda
|
||||
login:
|
||||
password: Palavra-passe
|
||||
submit: Login
|
||||
username: Nome de utilizador
|
||||
wrongCredentials: Dados errados
|
||||
prompts:
|
||||
copy: Copiar
|
||||
copyMessage: 'Escolhe um lugar para copiar os ficheiros:'
|
||||
currentlyNavigating: 'A navegar em:'
|
||||
deleteMessageMultiple: Deseja eliminar {count} ficheiro(s)?
|
||||
deleteMessageSingle: Deseja eliminar esta pasta/ficheiro?
|
||||
deleteTitle: Eliminar ficheiros
|
||||
displayName: 'Nome:'
|
||||
download: Descarregar ficheiros
|
||||
downloadMessage: Escolha o formato do ficheiro.
|
||||
error: Algo correu mal
|
||||
fileInfo: Informação do ficheiro
|
||||
filesSelected: "{count} ficheiros selecionados."
|
||||
lastModified: Última Modificação
|
||||
move: Mover
|
||||
moveMessage: 'Escolha uma nova casa para os seus ficheiros:'
|
||||
newDir: Nova pasta
|
||||
newDirMessage: Escreva o nome da nova pasta.
|
||||
newFile: Novo ficheiro
|
||||
newFileMessage: Escreva o nome do novo ficheiro.
|
||||
numberDirs: Número de pastas
|
||||
numberFiles: Número de ficheiros
|
||||
rename: Renomear
|
||||
renameMessage: Insira um novo nome para
|
||||
show: Mostrar
|
||||
size: Tamanho
|
||||
settings:
|
||||
admin: Admin
|
||||
administrator: Administrador
|
||||
allowCommands: Executar comandos
|
||||
allowEdit: Editar, renomear e eliminar ficheiros ou pastas
|
||||
allowNew: Criar novos ficheiros e pastas
|
||||
avoidChanges: "(deixe em branco para manter)"
|
||||
changePassword: Alterar Password
|
||||
commands: Comandos
|
||||
commandsHelp: >
|
||||
Pode definir um conjunto de comandos a executar em determiandos eventos. Deve
|
||||
escrever um comando por linha. Se o evento estiver relacionado com ficheiros,
|
||||
como antes e depois de guardar, irá existir uma variável de ambiente denominada
|
||||
"file" com o caminho do ficheiro.
|
||||
commandsUpdated: Comandos atualizados!
|
||||
customStylesheet: Estilos Personalizados
|
||||
examples: Exemplos
|
||||
globalSettings: Configurações Globais
|
||||
language: Linguagem
|
||||
newPassword: Nova palavra-passe
|
||||
newPasswordConfirm: Confirme a nova palavra-passe
|
||||
newUser: Novo Utilizador
|
||||
password: Palavra-passe
|
||||
passwordUpdated: Palavra-passe atualizada!
|
||||
permissions: Permissões
|
||||
permissionsHelp: >
|
||||
Pode definir o utilizador como administrador ou escolher as permissões manualmente.
|
||||
Se selecionar a opção "Administrador", todas as outras opções serão automaticamente
|
||||
selecionadas. A gestão dos utilizadores é um privilégio restringido aos administradores.
|
||||
pluginsUpdated: Plugins atualizados!
|
||||
profileSettings: Configurações do Utilizador
|
||||
ruleExample1: >
|
||||
previne o acesso a qualquer "dotfile" (como .git, .gitignore) em qualquer pasta
|
||||
ruleExample2: bloqueia o acesso ao ficheiro chamado Caddyfile.
|
||||
rules: Regras
|
||||
rulesHelp1: >
|
||||
Aqui pode definir um conjunto de regras para permitir ou bloquear o acesso
|
||||
do utilizador a determinados ficheiros ou pastas. Os ficheiros bloqueados não
|
||||
irão aparecer durante a navegação. Suportamos expressões regulares e os caminhos
|
||||
dos ficheiros devem ser relativos à base do utilizador.
|
||||
rulesHelp2: >
|
||||
Cada regra deve ser colocada numa linha diferente e deve começar com as
|
||||
palavras {0} (permite) ou {1} (bloqueia). Deve escrever, logo de seguida, {2},
|
||||
caso queira utilizar uma expressão regular. Depois, escreva o caminho do ficheiro/pasta
|
||||
ou a expressão regular.
|
||||
scope: Base
|
||||
settingsUpdated: Configurações atualizadas!
|
||||
user: Utilizador
|
||||
userCommands: Comandos
|
||||
userCommandsHelp:
|
||||
'Uma lista, separada com espaços, de comandos disponíveis para este
|
||||
utilizados. Exemplo:'
|
||||
userCreated: Utilizador criado!
|
||||
userDeleted: Utilizador eliminado!
|
||||
userManagement: Gestão de Utilizadores
|
||||
username: Nome de utilizador
|
||||
users: Utilizadores
|
||||
userUpdated: Utilizador atualizado!
|
||||
sidebar:
|
||||
help: Ajuda
|
||||
logout: Sair
|
||||
myFiles: Ficheiros
|
||||
newFile: Novo ficheiro
|
||||
newFolder: Nova pasta
|
||||
servedWith: Servido com
|
||||
settings: Configurações
|
||||
search:
|
||||
writeToSearch: Escreva aqui para pesquisar
|
||||
searchOrCommand: Pesquise ou execute um comando...
|
||||
searchOrSupportedCommand: 'Pesquise ou utilize um dos seus comandos:'
|
||||
search: Pesquise...
|
||||
type: Escreva e prima enter para pesquisar.
|
||||
pressToSearch: Prima enter para pesquisar.
|
||||
pressToExecute: Prima enter para executar.
|
||||
languages:
|
||||
en: Inglês
|
||||
pt: Português
|
||||
zhCN: Chinês (Simplificado)
|
|
@ -0,0 +1,151 @@
|
|||
buttons:
|
||||
cancel: 取消
|
||||
close: 关闭
|
||||
copy: 复制
|
||||
copyFile: 复制文件
|
||||
create: 创建
|
||||
delete: 删除
|
||||
download: 下载
|
||||
info: 信息
|
||||
more: 更多
|
||||
move: 移动
|
||||
moveFile: 移动文件
|
||||
new: 新
|
||||
next: 下一步
|
||||
ok: 确定
|
||||
previous: 以前
|
||||
rename: 重命名
|
||||
reportIssue: 报告问题
|
||||
save: 保存
|
||||
search: 搜索
|
||||
select: 选择
|
||||
selectMultiple: 选择多个
|
||||
switchView: 切换显示方式
|
||||
toggleSidebar: 切换侧边栏
|
||||
update: 更新
|
||||
upload: 上传
|
||||
errors:
|
||||
forbidden: 你被禁止访问.
|
||||
internal: 内部出现麻烦了.
|
||||
notFound: 找不到文件.
|
||||
files:
|
||||
folders: 文件夹
|
||||
files: 文件
|
||||
body: Body
|
||||
clear: 清理
|
||||
closePreview: 关闭预览
|
||||
home: 主页
|
||||
lastModified: 最后修改
|
||||
loading: 加载中...
|
||||
lonely: 这里没有任何文件...
|
||||
metadata: 元数据
|
||||
multipleSelectionEnabled: 启用多选模式(现在可以选择多个文件/文件夹)
|
||||
name: 名称
|
||||
size: 大小
|
||||
help:
|
||||
click: 选择文件或目录
|
||||
ctrl:
|
||||
click: 选择多个文件或目录
|
||||
f: 打开搜索框
|
||||
s: 保存文件或下载文件夹
|
||||
del: 删除 所选文件/文件夹
|
||||
doubleClick: 打开文件或目录
|
||||
esc: 清除 当前所有选择 或 关闭提示信息
|
||||
f1: 显示 当前帮助信息
|
||||
f2: 重命名 文件/文件夹
|
||||
help: 帮助
|
||||
login:
|
||||
password: 密码
|
||||
submit: 登录
|
||||
username: 用户名
|
||||
wrongCredentials: 账号或密码错误
|
||||
prompts:
|
||||
copy: 复制
|
||||
copyMessage: '请选择欲复制至的目录:'
|
||||
currentlyNavigating: '目前正在浏览:'
|
||||
deleteMessageMultiple: 你确定要删除这 {count} 个文件吗?
|
||||
deleteMessageSingle: 你确定要删除这个文件/文件夹吗?
|
||||
deleteTitle: 删除文件
|
||||
displayName: '名称:'
|
||||
download: 下载文件
|
||||
downloadMessage: 请选择要下载的压缩格式.
|
||||
error: 出了一点问题...
|
||||
fileInfo: 文件信息
|
||||
filesSelected: '选择 {count} 个文件.'
|
||||
lastModified: 最后修改
|
||||
move: 移动
|
||||
moveMessage: '请选择欲移动至的目录:'
|
||||
newDir: 新建目录
|
||||
newDirMessage: 请输入新建目录的名称.
|
||||
newFile: 新建文件
|
||||
newFileMessage: 请输入新建文件的名称.
|
||||
numberDirs: 目录数
|
||||
numberFiles: 文件数
|
||||
rename: 重命名
|
||||
renameMessage: '请输入新名称, 旧名称是:'
|
||||
show: 揭示
|
||||
size: 大小
|
||||
settings:
|
||||
admin: 管理员
|
||||
administrator: 管理员
|
||||
allowCommands: 执行命令(Linux 代码)
|
||||
allowEdit: 编辑、重命名或删除文件/目录.
|
||||
allowNew: 创建新文件和目录.
|
||||
avoidChanges: '(留空以避免更改)'
|
||||
changePassword: 更改密码
|
||||
commands: 命令(linux 代码)
|
||||
commandsHelp: >
|
||||
'Here you can set commands that are executed in the named events.
|
||||
每行一条命令. If the event is related to files, such as before and after saving,
|
||||
the environment variable "file" will be available with the path of the file.'
|
||||
commandsUpdated: 命令更新!
|
||||
customStylesheet: 自定义样式表
|
||||
examples: 例子
|
||||
globalSettings: 全局设置
|
||||
newPassword: 您的新密码
|
||||
newPasswordConfirm: 重输一遍新密码
|
||||
newUser: 新建用户
|
||||
password: 密码
|
||||
passwordUpdated: 密码更新!
|
||||
permissions: 权限
|
||||
permissionsHelp: >
|
||||
'您可以将该用户设置为管理员 或单独选择各项权限. 如果选择 "管理员(Administrator)" ,
|
||||
将自动检查所有其他选项, 并且该用户可以管理其他用户.'
|
||||
pluginsUpdated: 插件设置更新!
|
||||
profileSettings: 配置文件设置
|
||||
ruleExample1: >
|
||||
'阻止用户访问每个文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore).'
|
||||
ruleExample2: 阻止用户访问其目录范围内任何名为 Caddyfile 的文件/文件夹.
|
||||
rules: 规则
|
||||
rulesHelp1: >
|
||||
'这里您可以为特定用户制定一组允许或不允许的规则,
|
||||
阻止的文件将不会显示到列表中, 用户将无法访问, 支持相对于用户的范围.'
|
||||
rulesHelp2: >
|
||||
每行一条规则, 必须以关键词 {0} 或 {1} 开头. 如果使用正则表达式,
|
||||
然后使用表达式或路径, 则需要在第二列单词加入 {2} .
|
||||
scope: 目录范围
|
||||
user: 用户
|
||||
userCommands: 用户命令(Linux 代码)
|
||||
userCommandsHelp: '一个以空格分割的列表, 用于指定该用户可以执行的命令(Linux 代码), 例如:'
|
||||
userCreated: 用户创建!
|
||||
userDeleted: 用户删除!
|
||||
userManagement: 用户管理
|
||||
username: 用户名
|
||||
users: 用户
|
||||
userUpdated: 用户更新!
|
||||
sidebar:
|
||||
help: 帮助
|
||||
logout: 注销
|
||||
myFiles: 我的文件
|
||||
newFile: 新建文件
|
||||
newFolder: 新建文件夹
|
||||
servedWith: 服务提供
|
||||
settings: 设置
|
||||
search:
|
||||
writeToSearch: 请输入要搜索的内容
|
||||
searchOrCommand: 搜索或者执行命令(Linux 代码)...
|
||||
searchOrSupportedCommand: '搜索或使用您支持使用的命令(一次只能执行一个命令):'
|
||||
search: 搜索...
|
||||
type: 键入并按 Enter 键(回车)进行搜索.
|
||||
pressToSearch: 按 Enter 键(回车)进行搜索.
|
||||
pressToExecute: 按 Enter 键(回车)执行.
|
|
@ -2,6 +2,7 @@ import Vue from 'vue'
|
|||
import App from './App'
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
|
||||
Vue.config.productionTip = true
|
||||
|
||||
|
@ -10,6 +11,7 @@ new Vue({
|
|||
el: '#app',
|
||||
store,
|
||||
router,
|
||||
i18n,
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
})
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
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 GlobalSettings from '@/components/GlobalSettings'
|
||||
import ProfileSettings from '@/components/ProfileSettings'
|
||||
import error403 from '@/components/errors/403'
|
||||
import error404 from '@/components/errors/404'
|
||||
import error500 from '@/components/errors/500'
|
||||
import Login from '@/views/Login'
|
||||
import Layout from '@/views/Layout'
|
||||
import Files from '@/views/Files'
|
||||
import Users from '@/views/Users'
|
||||
import User from '@/views/User'
|
||||
import GlobalSettings from '@/views/GlobalSettings'
|
||||
import ProfileSettings from '@/views/ProfileSettings'
|
||||
import Error403 from '@/views/errors/403'
|
||||
import Error404 from '@/views/errors/404'
|
||||
import Error500 from '@/views/errors/500'
|
||||
import auth from '@/utils/auth.js'
|
||||
import store from '@/store'
|
||||
|
||||
|
@ -25,24 +25,18 @@ const router = new Router({
|
|||
component: Login,
|
||||
beforeEnter: function (to, from, next) {
|
||||
auth.loggedIn()
|
||||
.then(() => {
|
||||
next({ path: '/files' })
|
||||
})
|
||||
.catch(() => {
|
||||
document.title = 'Login'
|
||||
next()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: {
|
||||
path: '/files/'
|
||||
.then(() => {
|
||||
next({ path: '/files' })
|
||||
})
|
||||
.catch(() => {
|
||||
document.title = 'Login'
|
||||
next()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/*',
|
||||
component: Main,
|
||||
component: Layout,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
|
@ -75,17 +69,17 @@ const router = new Router({
|
|||
{
|
||||
path: '/403',
|
||||
name: 'Forbidden',
|
||||
component: error403
|
||||
component: Error403
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: 'Not Found',
|
||||
component: error404
|
||||
component: Error404
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: 'Internal Server Error',
|
||||
component: error500
|
||||
component: Error500
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
|
@ -95,12 +89,6 @@ const router = new Router({
|
|||
requiresAdmin: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/users/',
|
||||
redirect: {
|
||||
path: '/users'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/users/*',
|
||||
name: 'User',
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import i18n from '@/i18n'
|
||||
|
||||
const mutations = {
|
||||
closeHovers: state => {
|
||||
state.show = null
|
||||
|
@ -22,8 +24,10 @@ const mutations = {
|
|||
},
|
||||
setLoading: (state, value) => { state.loading = value },
|
||||
setReload: (state, value) => { state.reload = value },
|
||||
setUser: (state, value) => (state.user = value),
|
||||
setUserCSS: (state, value) => (state.user.css = value),
|
||||
setUser: (state, value) => {
|
||||
i18n.locale = value.locale
|
||||
state.user = value
|
||||
},
|
||||
setJWT: (state, value) => (state.jwt = value),
|
||||
multiple: (state, value) => (state.multiple = value),
|
||||
addSelected: (state, value) => (state.selected.push(value)),
|
||||
|
|
|
@ -2,7 +2,7 @@ import store from '@/store'
|
|||
|
||||
const ssl = (window.location.protocol === 'https:')
|
||||
|
||||
function removePrefix (url) {
|
||||
export function removePrefix (url) {
|
||||
if (url.startsWith('/files')) {
|
||||
return url.slice(6)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ function removePrefix (url) {
|
|||
return url
|
||||
}
|
||||
|
||||
function fetch (url) {
|
||||
export function fetch (url) {
|
||||
url = removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -24,10 +24,7 @@ function fetch (url) {
|
|||
resolve(JSON.parse(request.responseText))
|
||||
break
|
||||
default:
|
||||
reject({
|
||||
message: request.responseText,
|
||||
status: request.status
|
||||
})
|
||||
reject(new Error(request.status))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +33,7 @@ function fetch (url) {
|
|||
})
|
||||
}
|
||||
|
||||
function rm (url) {
|
||||
export function rm (url) {
|
||||
url = removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -57,7 +54,7 @@ function rm (url) {
|
|||
})
|
||||
}
|
||||
|
||||
function post (url, content = '') {
|
||||
export function post (url, content = '') {
|
||||
url = removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -78,7 +75,7 @@ function post (url, content = '') {
|
|||
})
|
||||
}
|
||||
|
||||
function put (url, content = '') {
|
||||
export function put (url, content = '') {
|
||||
url = removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -132,15 +129,15 @@ function moveCopy (items, copy = false) {
|
|||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
function move (items) {
|
||||
export function move (items) {
|
||||
return moveCopy(items)
|
||||
}
|
||||
|
||||
function copy (items) {
|
||||
export function copy (items) {
|
||||
return moveCopy(items, true)
|
||||
}
|
||||
|
||||
function checksum (url, algo) {
|
||||
export function checksum (url, algo) {
|
||||
url = removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -160,7 +157,7 @@ function checksum (url, algo) {
|
|||
})
|
||||
}
|
||||
|
||||
function command (url, command, onmessage, onclose) {
|
||||
export function command (url, command, onmessage, onclose) {
|
||||
let protocol = (ssl ? 'wss:' : 'ws:')
|
||||
url = removePrefix(url)
|
||||
url = `${protocol}//${window.location.host}${store.state.baseURL}/api/command${url}`
|
||||
|
@ -171,7 +168,7 @@ function command (url, command, onmessage, onclose) {
|
|||
conn.onclose = onclose
|
||||
}
|
||||
|
||||
function search (url, search, onmessage, onclose) {
|
||||
export function search (url, search, onmessage, onclose) {
|
||||
let protocol = (ssl ? 'wss:' : 'ws:')
|
||||
url = removePrefix(url)
|
||||
url = `${protocol}//${window.location.host}${store.state.baseURL}/api/search${url}`
|
||||
|
@ -182,7 +179,7 @@ function search (url, search, onmessage, onclose) {
|
|||
conn.onclose = onclose
|
||||
}
|
||||
|
||||
function download (format, ...files) {
|
||||
export function download (format, ...files) {
|
||||
let url = `${store.state.baseURL}/api/download`
|
||||
|
||||
if (files.length === 1) {
|
||||
|
@ -206,7 +203,59 @@ function download (format, ...files) {
|
|||
window.open(url)
|
||||
}
|
||||
|
||||
function getUsers () {
|
||||
export function getSettings () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/settings/`, 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 function updateSettings (param, which) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = {
|
||||
what: 'settings',
|
||||
which: which,
|
||||
data: {}
|
||||
}
|
||||
|
||||
data.data[which] = param
|
||||
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/settings/`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
case 200:
|
||||
resolve()
|
||||
break
|
||||
default:
|
||||
reject(request.responseText)
|
||||
break
|
||||
}
|
||||
}
|
||||
request.onerror = (error) => { reject(error) }
|
||||
request.send(JSON.stringify(data))
|
||||
})
|
||||
}
|
||||
|
||||
// USERS
|
||||
|
||||
export function getUsers () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/users/`, true)
|
||||
|
@ -227,7 +276,7 @@ function getUsers () {
|
|||
})
|
||||
}
|
||||
|
||||
function getUser (id) {
|
||||
export function getUser (id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/users/${id}`, true)
|
||||
|
@ -248,7 +297,7 @@ function getUser (id) {
|
|||
})
|
||||
}
|
||||
|
||||
function newUser (user) {
|
||||
export function newUser (user) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${store.state.baseURL}/api/users/`, true)
|
||||
|
@ -265,11 +314,15 @@ function newUser (user) {
|
|||
}
|
||||
}
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send(JSON.stringify(user))
|
||||
request.send(JSON.stringify({
|
||||
what: 'user',
|
||||
which: 'new',
|
||||
data: user
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
export function updateUser (user, which) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/users/${user.ID}`, true)
|
||||
|
@ -286,11 +339,15 @@ function updateUser (user) {
|
|||
}
|
||||
}
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send(JSON.stringify(user))
|
||||
request.send(JSON.stringify({
|
||||
what: 'user',
|
||||
which: (typeof which === 'string') ? which : 'all',
|
||||
data: user
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function deleteUser (id) {
|
||||
export function deleteUser (id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('DELETE', `${store.state.baseURL}/api/users/${id}`, true)
|
||||
|
@ -311,133 +368,8 @@ function deleteUser (id) {
|
|||
})
|
||||
}
|
||||
|
||||
function updatePassword (password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/users/change-password`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
case 200:
|
||||
resolve()
|
||||
break
|
||||
default:
|
||||
reject(request.responseText)
|
||||
break
|
||||
}
|
||||
}
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send(JSON.stringify({ 'password': password }))
|
||||
})
|
||||
}
|
||||
|
||||
function updateCSS (css) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/users/change-css`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
case 200:
|
||||
resolve()
|
||||
break
|
||||
default:
|
||||
reject(request.responseText)
|
||||
break
|
||||
}
|
||||
}
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send(JSON.stringify({ 'css': css }))
|
||||
})
|
||||
}
|
||||
|
||||
function getCommands () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/commands/`, 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()
|
||||
})
|
||||
}
|
||||
|
||||
function updateCommands (commands) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/commands/`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
case 200:
|
||||
resolve()
|
||||
break
|
||||
default:
|
||||
reject(request.responseText)
|
||||
break
|
||||
}
|
||||
}
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send(JSON.stringify(commands))
|
||||
})
|
||||
}
|
||||
|
||||
function getPlugins () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/plugins/`, 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()
|
||||
})
|
||||
}
|
||||
|
||||
function updatePlugins (data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/plugins/`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
case 200:
|
||||
resolve()
|
||||
break
|
||||
default:
|
||||
reject(request.responseText)
|
||||
break
|
||||
}
|
||||
}
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send(JSON.stringify(data))
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
removePrefix,
|
||||
delete: rm,
|
||||
fetch,
|
||||
checksum,
|
||||
|
@ -448,16 +380,13 @@ export default {
|
|||
command,
|
||||
search,
|
||||
download,
|
||||
getUser,
|
||||
// other things
|
||||
getSettings,
|
||||
updateSettings,
|
||||
// User things
|
||||
newUser,
|
||||
updateUser,
|
||||
getUser,
|
||||
getUsers,
|
||||
updatePassword,
|
||||
updateCSS,
|
||||
getCommands,
|
||||
updateCommands,
|
||||
removePrefix,
|
||||
getPlugins,
|
||||
updatePlugins,
|
||||
updateUser,
|
||||
deleteUser
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ function loggedIn () {
|
|||
parseToken(request.responseText)
|
||||
resolve()
|
||||
} else {
|
||||
reject()
|
||||
reject(new Error(request.responseText))
|
||||
}
|
||||
}
|
||||
request.onerror = () => reject()
|
||||
request.onerror = () => reject(new Error('Could not finish the request'))
|
||||
request.send()
|
||||
})
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ function login (user, password) {
|
|||
reject(request.responseText)
|
||||
}
|
||||
}
|
||||
request.onerror = () => reject()
|
||||
request.onerror = () => reject(new Error('Could not finish the request'))
|
||||
request.send(JSON.stringify(data))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="breadcrumbs">
|
||||
<router-link to="/files/">
|
||||
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</router-link>
|
||||
|
||||
|
@ -11,8 +11,8 @@
|
|||
</span>
|
||||
</div>
|
||||
<div v-if="error">
|
||||
<not-found v-if="error === 404"></not-found>
|
||||
<forbidden v-else-if="error === 403"></forbidden>
|
||||
<not-found v-if="error.message === '404'"></not-found>
|
||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||
<internal-error v-else></internal-error>
|
||||
</div>
|
||||
<editor v-else-if="isEditor"></editor>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<preview v-else-if="isPreview"></preview>
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<span>Loading...</span>
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,9 +30,9 @@
|
|||
import Forbidden from './errors/403'
|
||||
import NotFound from './errors/404'
|
||||
import InternalError from './errors/500'
|
||||
import Preview from './Preview'
|
||||
import Listing from './Listing'
|
||||
import Editor from './Editor'
|
||||
import Preview from '@/components/files/Preview'
|
||||
import Listing from '@/components/files/Listing'
|
||||
import Editor from '@/components/files/Editor'
|
||||
import api from '@/utils/api'
|
||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||
|
||||
|
@ -116,20 +116,11 @@ export default {
|
|||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('scroll', event => {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return
|
||||
|
||||
let top = 112 - window.scrollY
|
||||
|
||||
if (top < 64) {
|
||||
top = 64
|
||||
}
|
||||
|
||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||
})
|
||||
window.addEventListener('scroll', this.scroll)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('scroll', this.scroll)
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
|
@ -152,25 +143,19 @@ export default {
|
|||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
api.fetch(url)
|
||||
.then((req) => {
|
||||
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||
}
|
||||
.then((req) => {
|
||||
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||
}
|
||||
|
||||
this.$store.commit('updateRequest', req)
|
||||
document.title = req.name
|
||||
this.setLoading(false)
|
||||
})
|
||||
.catch(error => {
|
||||
this.setLoading(false)
|
||||
|
||||
if (typeof error === 'object') {
|
||||
this.error = error.status
|
||||
return
|
||||
}
|
||||
|
||||
this.error = error
|
||||
})
|
||||
this.$store.commit('updateRequest', req)
|
||||
document.title = req.name
|
||||
this.setLoading(false)
|
||||
})
|
||||
.catch(error => {
|
||||
this.setLoading(false)
|
||||
this.error = error
|
||||
})
|
||||
},
|
||||
keyEvent (event) {
|
||||
// Esc!
|
||||
|
@ -220,11 +205,21 @@ export default {
|
|||
|
||||
if (this.req.kind !== 'editor') {
|
||||
document.getElementById('download-button').click()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll (event) {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.req.display === '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')
|
||||
},
|
|
@ -1,12 +1,20 @@
|
|||
<template>
|
||||
<div class="dashboard">
|
||||
<h1>Global Settings</h1>
|
||||
|
||||
<ul>
|
||||
<li><router-link to="/settings/profile">Go to Profile Settings</router-link></li>
|
||||
<li><router-link to="/users">Go to User Management</router-link></li>
|
||||
<ul id="nav">
|
||||
<li>
|
||||
<router-link to="/settings/profile">
|
||||
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.profileSettings') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/users">
|
||||
{{ $t('settings.userManagement') }} <i class="material-icons">keyboard_arrow_right</i>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>{{ $t('settings.globalSettings') }}</h1>
|
||||
|
||||
<form @submit="savePlugin" v-if="plugins.length > 0">
|
||||
<template v-for="plugin in plugins">
|
||||
<h2>{{ capitalize(plugin.name) }}</h2>
|
||||
|
@ -23,11 +31,9 @@
|
|||
</form>
|
||||
|
||||
<form @submit="saveCommands">
|
||||
<h2>Commands</h2>
|
||||
<h2>{{ $t('settings.commands') }}</h2>
|
||||
|
||||
<p class="small">Here you can set commands that are executed in the named events. You write one command
|
||||
per line. If the event is related to files, such as before and after saving, the environment variable
|
||||
<code>file</code> will be available with the path of the file.</p>
|
||||
<p class="small">{{ $t('settings.commandsHelp') }}</p>
|
||||
|
||||
<template v-for="command in commands">
|
||||
<h3>{{ capitalize(command.name) }}</h3>
|
||||
|
@ -42,7 +48,7 @@
|
|||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import api from '@/utils/api'
|
||||
import { getSettings, updateSettings } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
|
@ -56,24 +62,20 @@ export default {
|
|||
...mapState([ 'user' ])
|
||||
},
|
||||
created () {
|
||||
api.getCommands()
|
||||
.then(commands => {
|
||||
for (let key in commands) {
|
||||
getSettings()
|
||||
.then(settings => {
|
||||
for (let key in settings.plugins) {
|
||||
this.plugins.push(this.parsePlugin(key, settings.plugins[key]))
|
||||
}
|
||||
|
||||
for (let key in settings.commands) {
|
||||
this.commands.push({
|
||||
name: key,
|
||||
value: commands[key].join('\n')
|
||||
value: settings.commands[key].join('\n')
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(error => { this.showError(error) })
|
||||
|
||||
api.getPlugins()
|
||||
.then(plugins => {
|
||||
for (let key in plugins) {
|
||||
this.plugins.push(this.parsePlugin(key, plugins[key]))
|
||||
}
|
||||
})
|
||||
.catch(error => { this.showError(error) })
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showSuccess', 'showError' ]),
|
||||
|
@ -102,8 +104,8 @@ export default {
|
|||
commands[command.name] = value
|
||||
}
|
||||
|
||||
api.updateCommands(commands)
|
||||
.then(() => { this.showSuccess('Commands updated!') })
|
||||
updateSettings(commands, 'commands')
|
||||
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
|
||||
.catch(error => { this.showError(error) })
|
||||
},
|
||||
savePlugin (event) {
|
||||
|
@ -129,10 +131,8 @@ export default {
|
|||
plugins[plugin.name] = p
|
||||
}
|
||||
|
||||
console.log(plugins)
|
||||
|
||||
api.updatePlugins(plugins)
|
||||
.then(() => { this.showSuccess('Plugins settings updated!') })
|
||||
updateSettings(plugins, 'plugins')
|
||||
.then(() => { this.showSuccess(this.$t('settings.pluginsUpdated')) })
|
||||
.catch(error => { this.showError(error) })
|
||||
},
|
||||
parsePlugin (name, plugin) {
|
|
@ -10,13 +10,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Search from './Search'
|
||||
import Sidebar from './Sidebar'
|
||||
import Prompts from './prompts/Prompts'
|
||||
import SiteHeader from './Header'
|
||||
import Search from '@/components/Search'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
import Prompts from '@/components/prompts/Prompts'
|
||||
import SiteHeader from '@/components/Header'
|
||||
|
||||
export default {
|
||||
name: 'main',
|
||||
name: 'layout',
|
||||
components: {
|
||||
Search,
|
||||
Sidebar,
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div id="login">
|
||||
<form @submit="submit">
|
||||
<img src="../assets/logo.svg" alt="File Manager">
|
||||
<h1>File Manager</h1>
|
||||
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
|
||||
<input type="text" v-model="username" :placeholder="$t('login.username')">
|
||||
<input type="password" v-model="password" :placeholder="$t('login.password')">
|
||||
<input type="submit" :value="$t('login.submit')">
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import auth from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
data: function () {
|
||||
return {
|
||||
wrong: false,
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit: function (event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
let redirect = this.$route.query.redirect
|
||||
if (redirect === '' || redirect === undefined || redirect === null) {
|
||||
redirect = '/files/'
|
||||
}
|
||||
|
||||
auth.login(this.username, this.password)
|
||||
.then(() => { this.$router.push({ path: redirect }) })
|
||||
.catch(() => { this.wrong = true })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="dashboard">
|
||||
<ul id="nav" v-if="user.admin">
|
||||
<li>
|
||||
<router-link to="/settings/global">
|
||||
{{ $t('settings.globalSettings') }} <i class="material-icons">keyboard_arrow_right</i>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>{{ $t('settings.profileSettings') }}</h1>
|
||||
|
||||
<form @submit="updateSettings">
|
||||
<h3>{{ $t('settings.language') }}</h3>
|
||||
<p><languages id="locale" :selected.sync="locale"></languages></p>
|
||||
<h3>{{ $t('settings.customStylesheet') }}</h3>
|
||||
<textarea v-model="css" name="css"></textarea>
|
||||
<p><input type="submit" :value="$t('buttons.update')"></p>
|
||||
</form>
|
||||
|
||||
<form @submit="updatePassword">
|
||||
<h3>{{ $t('settings.changePassword') }}</h3>
|
||||
<p><input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password"></p>
|
||||
<p><input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password"></p>
|
||||
<p><input type="submit" :value="$t('buttons.update')"></p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { updateUser } from '@/utils/api'
|
||||
import Languages from '@/components/Languages'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
components: {
|
||||
Languages
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
password: '',
|
||||
passwordConf: '',
|
||||
css: '',
|
||||
locale: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ]),
|
||||
passwordClass () {
|
||||
if (this.password === '' && this.passwordConf === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (this.password === this.passwordConf) {
|
||||
return 'green'
|
||||
}
|
||||
|
||||
return 'red'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.css = this.user.css
|
||||
this.locale = this.user.locale
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showSuccess' ]),
|
||||
updatePassword (event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.password !== this.passwordConf) {
|
||||
return
|
||||
}
|
||||
|
||||
let user = {
|
||||
ID: this.$store.state.user.ID,
|
||||
password: this.password
|
||||
}
|
||||
|
||||
updateUser(user, 'password').then(location => {
|
||||
this.showSuccess(this.$t('settings.passwordUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
},
|
||||
updateSettings (event) {
|
||||
event.preventDefault()
|
||||
|
||||
let user = {...this.$store.state.user}
|
||||
user.css = this.css
|
||||
user.locale = this.locale
|
||||
|
||||
updateUser(user, 'partial').then(location => {
|
||||
this.$store.commit('setUser', user)
|
||||
this.$emit('css-updated')
|
||||
this.showSuccess(this.$t('settings.settingsUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,58 +1,56 @@
|
|||
<template>
|
||||
<div>
|
||||
<form @submit="save" class="dashboard">
|
||||
<h1 v-if="id === 0">New User</h1>
|
||||
<h1 v-else>User {{ username }}</h1>
|
||||
<h1 v-if="id === 0">{{ $t('settings.newUser') }}</h1>
|
||||
<h1 v-else>{{ $t('settings.user') }} {{ username }}</h1>
|
||||
|
||||
<p><label for="username">Username</label><input type="text" v-model="username" id="username"></p>
|
||||
<p><label for="password">Password</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
|
||||
<p><label for="scope">Scope</label><input type="text" v-model="filesystem" id="scope"></p>
|
||||
<p><label for="username">{{ $t('settings.username') }}</label><input type="text" v-model="username" id="username"></p>
|
||||
<p><label for="password">{{ $t('settings.password') }}</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
|
||||
<p><label for="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
|
||||
<p>
|
||||
<label for="locale">{{ $t('settings.language') }}</label>
|
||||
<languages id="locale" :selected.sync="locale"></languages>
|
||||
</p>
|
||||
|
||||
<h2>Permissions</h2>
|
||||
<h2>{{ $t('settings.permissions') }}</h2>
|
||||
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
|
||||
|
||||
<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>
|
||||
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> {{ $t('settings.allowEdit') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> {{ $t('settings.allowCommands') }}</p>
|
||||
<p v-for="(value, key) in permissions" :key="key">
|
||||
<input type="checkbox" :disabled="admin" v-model="permissions[key]"> {{ capitalize(key) }}
|
||||
</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>
|
||||
|
||||
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||
<input type="text" v-model.trim="commands">
|
||||
|
||||
<h2>Rules</h2>
|
||||
<h2>{{ $t('settings.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">{{ $t('settings.rulesHelp1') }}</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>
|
||||
<i18n path="settings.rulesHelp2" tag="p" class="small">
|
||||
<code>allow</code><code>disallow</code><code>regex</code>
|
||||
</i18n>
|
||||
|
||||
<p class="small"><strong>Examples</strong></p>
|
||||
<p class="small"><strong>{{ $t('settings.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>
|
||||
<li><code>disallow regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</li>
|
||||
<li><code>disallow /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
|
||||
</ul>
|
||||
|
||||
<textarea v-model.trim="rules"></textarea>
|
||||
|
||||
<h2>Custom Stylesheet</h2>
|
||||
<h2>{{ $t('settings.customStylesheet') }}</h2>
|
||||
|
||||
<textarea name="css"></textarea>
|
||||
|
||||
<p>
|
||||
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="delete">Delete</button>
|
||||
<input type="submit" value="Save">
|
||||
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="delete" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
<input type="submit" :value="$t('buttons.save')">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
|
@ -60,8 +58,13 @@
|
|||
<h3>Delete User</h3>
|
||||
<p>Are you sure you want to delete this user?</p>
|
||||
<div>
|
||||
<button @click="deleteUser" autofocus>Delete</button>
|
||||
<button @click="closeHovers" class="cancel">Cancel</button>
|
||||
<button @click="deleteUser" autofocus>{{ $t('buttons.delete') }}</button>
|
||||
<button class="cancel"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">
|
||||
{{ $t('buttons.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,10 +72,12 @@
|
|||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import api from '@/utils/api'
|
||||
import { getUser, newUser, updateUser, deleteUser } from '@/utils/api'
|
||||
import Languages from '@/components/Languages'
|
||||
|
||||
export default {
|
||||
name: 'user',
|
||||
components: { Languages },
|
||||
data: () => {
|
||||
return {
|
||||
id: 0,
|
||||
|
@ -85,6 +90,7 @@ export default {
|
|||
username: '',
|
||||
filesystem: '',
|
||||
rules: '',
|
||||
locale: '',
|
||||
css: '',
|
||||
commands: ''
|
||||
}
|
||||
|
@ -92,7 +98,7 @@ export default {
|
|||
computed: {
|
||||
passwordPlaceholder () {
|
||||
if (this.$route.path === '/users/new') return ''
|
||||
return '(leave blank to avoid changes)'
|
||||
return this.$t('settings.avoidChanges')
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -119,7 +125,7 @@ export default {
|
|||
user = 'base'
|
||||
}
|
||||
|
||||
api.getUser(user).then(user => {
|
||||
getUser(user).then(user => {
|
||||
this.id = user.ID
|
||||
this.admin = user.admin
|
||||
this.allowCommands = user.allowCommands
|
||||
|
@ -130,6 +136,7 @@ export default {
|
|||
this.commands = user.commands.join(' ')
|
||||
this.css = user.css
|
||||
this.permissions = user.permissions
|
||||
this.locale = user.locale
|
||||
|
||||
for (let rule of user.rules) {
|
||||
if (rule.allow) {
|
||||
|
@ -173,6 +180,7 @@ export default {
|
|||
this.username = ''
|
||||
this.filesystem = ''
|
||||
this.rules = ''
|
||||
this.locale = ''
|
||||
this.css = ''
|
||||
this.commands = ''
|
||||
},
|
||||
|
@ -182,9 +190,9 @@ export default {
|
|||
deleteUser (event) {
|
||||
event.preventDefault()
|
||||
|
||||
api.deleteUser(this.id).then(location => {
|
||||
deleteUser(this.id).then(location => {
|
||||
this.$router.push({ path: '/users' })
|
||||
this.$store.commit('showSuccess', 'User deleted!')
|
||||
this.$store.commit('showSuccess', this.$t('settings.userDeleted'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
|
@ -194,9 +202,9 @@ export default {
|
|||
let user = this.parseForm()
|
||||
|
||||
if (this.$route.path === '/users/new') {
|
||||
api.newUser(user).then(location => {
|
||||
newUser(user).then(location => {
|
||||
this.$router.push({ path: location })
|
||||
this.$store.commit('showSuccess', 'User created!')
|
||||
this.$store.commit('showSuccess', this.$t('settings.userCreated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
|
@ -204,8 +212,12 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
api.updateUser(user).then(location => {
|
||||
this.$store.commit('showSuccess', 'User updated!')
|
||||
updateUser(user).then(location => {
|
||||
if (user.ID === this.$store.state.user.ID) {
|
||||
this.$store.commit('setUser', user)
|
||||
}
|
||||
|
||||
this.$store.commit('showSuccess', this.$t('settings.userUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
|
@ -222,6 +234,7 @@ export default {
|
|||
allowEdit: this.allowEdit,
|
||||
permissions: this.permissions,
|
||||
css: this.css,
|
||||
locale: this.locale,
|
||||
commands: this.commands.split(' '),
|
||||
rules: []
|
||||
}
|
||||
|
@ -269,7 +282,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="dashboard">
|
||||
<h1>Users <router-link to="/users/new"><button>New</button></router-link></h1>
|
||||
<h1>{{ $t('settings.users') }} <router-link to="/users/new"><button>{{ $t('buttons.new') }}</button></router-link></h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Admin</th>
|
||||
<th>Scope</th>
|
||||
<th>{{ $t('settings.username') }}</th>
|
||||
<th>{{ $t('settings.admin') }}</th>
|
||||
<th>{{ $t('settings.scope') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">error</i>
|
||||
<span>You're not welcome here.</span>
|
||||
<span>{{ $t('errors.forbidden') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">gps_off</i>
|
||||
<span>This location can't be reached.</span>
|
||||
<span>{{ $t('errors.notFound') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">error_outline</i>
|
||||
<span>Something really went wrong.</span>
|
||||
<span>{{ $t('errors.internal') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
8
auth.go
8
auth.go
|
@ -27,7 +27,7 @@ func authHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int
|
|||
}
|
||||
|
||||
// Checks if the user exists.
|
||||
u, ok := c.FM.Users[cred.Username]
|
||||
u, ok := c.Users[cred.Username]
|
||||
if !ok {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func printToken(c *RequestContext, w http.ResponseWriter) (int, error) {
|
|||
|
||||
// Creates the token and signs it.
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
string, err := token.SignedString(c.FM.key)
|
||||
string, err := token.SignedString(c.key)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
|
@ -114,7 +114,7 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
|||
// User if it is valid.
|
||||
func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||
return c.FM.key, nil
|
||||
return c.key, nil
|
||||
}
|
||||
var claims claims
|
||||
token, err := request.ParseFromRequestWithClaims(r,
|
||||
|
@ -127,7 +127,7 @@ func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
u, ok := c.FM.Users[claims.User.Username]
|
||||
u, ok := c.Users[claims.User.Username]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ var (
|
|||
commands string
|
||||
logfile string
|
||||
plugin string
|
||||
locale string
|
||||
port int
|
||||
allowCommands bool
|
||||
allowEdit bool
|
||||
|
@ -47,6 +48,7 @@ func init() {
|
|||
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
|
||||
flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
|
||||
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
|
||||
flag.StringVar(&locale, "locale", "en", "Default locale for new users")
|
||||
flag.StringVar(&plugin, "plugin", "", "Plugin you want to enable")
|
||||
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
||||
}
|
||||
|
@ -62,6 +64,7 @@ func setupViper() {
|
|||
viper.SetDefault("AllowEdit", true)
|
||||
viper.SetDefault("AllowNew", true)
|
||||
viper.SetDefault("Plugin", "")
|
||||
viper.SetDefault("Locale", "en")
|
||||
|
||||
viper.BindPFlag("Port", flag.Lookup("port"))
|
||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||
|
@ -72,6 +75,7 @@ func setupViper() {
|
|||
viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
|
||||
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
||||
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
||||
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
||||
viper.BindPFlag("Plugin", flag.Lookup("plugin"))
|
||||
|
||||
viper.SetConfigName("filemanager")
|
||||
|
@ -133,6 +137,7 @@ func main() {
|
|||
AllowNew: viper.GetBool("AllowNew"),
|
||||
Commands: viper.GetStringSlice("Commands"),
|
||||
Rules: []*filemanager.Rule{},
|
||||
Locale: viper.GetString("Locale"),
|
||||
CSS: "",
|
||||
FileSystem: fileutils.Dir(viper.GetString("Scope")),
|
||||
})
|
||||
|
|
12
download.go
12
download.go
|
@ -20,14 +20,14 @@ func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
|
||||
// If the file isn't a directory, serve it using http.ServeFile. We display it
|
||||
// inline if it is requested.
|
||||
if !c.FI.IsDir {
|
||||
if !c.File.IsDir {
|
||||
if r.URL.Query().Get("inline") == "true" {
|
||||
w.Header().Set("Content-Disposition", "inline")
|
||||
} else {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+c.FI.Name)
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+c.File.Name)
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, c.FI.Path)
|
||||
http.ServeFile(w, r, c.File.Path)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -46,10 +46,10 @@ func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
|
||||
// Clean the slashes.
|
||||
name = fileutils.SlashClean(name)
|
||||
files = append(files, filepath.Join(c.FI.Path, name))
|
||||
files = append(files, filepath.Join(c.File.Path, name))
|
||||
}
|
||||
} else {
|
||||
files = append(files, c.FI.Path)
|
||||
files = append(files, c.File.Path)
|
||||
}
|
||||
|
||||
// If the format is true, just set it to "zip".
|
||||
|
@ -93,7 +93,7 @@ func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// Defines the file name.
|
||||
name := c.FI.Name
|
||||
name := c.File.Name
|
||||
if name == "." || name == "" {
|
||||
name = "download"
|
||||
}
|
||||
|
|
2
file.go
2
file.go
|
@ -110,7 +110,7 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
|
|||
func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||
// Gets the directory information using the Virtual File System of
|
||||
// the user configuration.
|
||||
f, err := c.User.FileSystem.OpenFile(c.FI.VirtualPath, os.O_RDONLY, 0)
|
||||
f, err := c.User.FileSystem.OpenFile(c.File.VirtualPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -70,11 +70,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
errUserExist = errors.New("user already exists")
|
||||
errUserNotExist = errors.New("user does not exist")
|
||||
errEmptyRequest = errors.New("request body is empty")
|
||||
errEmptyPassword = errors.New("password is empty")
|
||||
plugins = map[string]Plugin{}
|
||||
errUserExist = errors.New("user already exists")
|
||||
errUserNotExist = errors.New("user does not exist")
|
||||
errEmptyRequest = errors.New("request body is empty")
|
||||
errEmptyPassword = errors.New("password is empty")
|
||||
errEmptyUsername = errors.New("username is empty")
|
||||
errEmptyScope = errors.New("scope is empty")
|
||||
errWrongDataType = errors.New("wrong data type")
|
||||
errInvalidUpdateField = errors.New("invalid field to update")
|
||||
plugins = map[string]Plugin{}
|
||||
)
|
||||
|
||||
// FileManager is a file manager instance. It should be creating using the
|
||||
|
@ -139,6 +143,9 @@ type User struct {
|
|||
// Custom styles for this user.
|
||||
CSS string `json:"css"`
|
||||
|
||||
// Locale is the language of the user.
|
||||
Locale string `json:"locale"`
|
||||
|
||||
// These indicate if the user can perform certain actions.
|
||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
||||
|
@ -208,6 +215,7 @@ var DefaultUser = User{
|
|||
Rules: []*Rule{},
|
||||
CSS: "",
|
||||
Admin: true,
|
||||
Locale: "en",
|
||||
FileSystem: fileutils.Dir("."),
|
||||
}
|
||||
|
||||
|
@ -428,23 +436,25 @@ func (m *FileManager) registerPermission(name string, value bool) error {
|
|||
// Compatible with http.Handler.
|
||||
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
code, err := serveHTTP(&RequestContext{
|
||||
FM: m,
|
||||
User: nil,
|
||||
FI: nil,
|
||||
FileManager: m,
|
||||
User: nil,
|
||||
File: nil,
|
||||
}, w, r)
|
||||
|
||||
if code != 0 {
|
||||
if code >= 400 {
|
||||
w.WriteHeader(code)
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
w.Write([]byte(err.Error()))
|
||||
} else {
|
||||
if err == nil {
|
||||
txt := http.StatusText(code)
|
||||
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
||||
w.Write([]byte(txt))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// Allowed checks if the user has permission to access a directory/file.
|
||||
|
|
34
http.go
34
http.go
|
@ -10,9 +10,9 @@ import (
|
|||
|
||||
// RequestContext contains the needed information to make handlers work.
|
||||
type RequestContext struct {
|
||||
*FileManager
|
||||
User *User
|
||||
FM *FileManager
|
||||
FI *file
|
||||
File *file
|
||||
// On API handlers, Router is the APi handler we want.
|
||||
Router string
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ type RequestContext struct {
|
|||
func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
|
||||
// returns a 404 error because we're not supposed to be here!
|
||||
p := strings.TrimPrefix(r.URL.Path, c.FM.BaseURL)
|
||||
p := strings.TrimPrefix(r.URL.Path, c.BaseURL)
|
||||
|
||||
if len(p) >= len(r.URL.Path) && c.FM.BaseURL != "" {
|
||||
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
if r.URL.Path == "/sw.js" {
|
||||
return renderFile(
|
||||
w,
|
||||
c.FM.assets.MustString("sw.js"),
|
||||
c.assets.MustString("sw.js"),
|
||||
"application/javascript",
|
||||
c,
|
||||
)
|
||||
|
@ -65,7 +65,7 @@ func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
|
||||
return renderFile(
|
||||
w,
|
||||
c.FM.assets.MustString("index.html"),
|
||||
c.assets.MustString("index.html"),
|
||||
"text/html",
|
||||
c,
|
||||
)
|
||||
|
@ -74,13 +74,13 @@ func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
// staticHandler handles the static assets path.
|
||||
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Path != "/static/manifest.json" {
|
||||
http.FileServer(c.FM.assets.HTTPBox()).ServeHTTP(w, r)
|
||||
http.FileServer(c.assets.HTTPBox()).ServeHTTP(w, r)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return renderFile(
|
||||
w,
|
||||
c.FM.assets.MustString("static/manifest.json"),
|
||||
c.assets.MustString("static/manifest.json"),
|
||||
"application/json",
|
||||
c,
|
||||
)
|
||||
|
@ -107,7 +107,7 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
for p := range c.FM.Plugins {
|
||||
for p := range c.Plugins {
|
||||
code, err := plugins[p].Handler.Before(c, w, r)
|
||||
if code != 0 || err != nil {
|
||||
return code, err
|
||||
|
@ -116,7 +116,7 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
|
||||
if c.Router == "checksum" || c.Router == "download" {
|
||||
var err error
|
||||
c.FI, err = getInfo(r.URL, c.FM, c.User)
|
||||
c.File, err = getInfo(r.URL, c.FileManager, c.User)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
@ -138,10 +138,8 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
code, err = resourceHandler(c, w, r)
|
||||
case "users":
|
||||
code, err = usersHandler(c, w, r)
|
||||
case "commands":
|
||||
code, err = commandsHandler(c, w, r)
|
||||
case "plugins":
|
||||
code, err = pluginsHandler(c, w, r)
|
||||
case "settings":
|
||||
code, err = settingsHandler(c, w, r)
|
||||
default:
|
||||
code = http.StatusNotFound
|
||||
}
|
||||
|
@ -150,7 +148,7 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
return code, err
|
||||
}
|
||||
|
||||
for p := range c.FM.Plugins {
|
||||
for p := range c.Plugins {
|
||||
code, err := plugins[p].Handler.After(c, w, r)
|
||||
if code != 0 || err != nil {
|
||||
return code, err
|
||||
|
@ -164,7 +162,7 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
func checksumHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
query := r.URL.Query().Get("algo")
|
||||
|
||||
val, err := c.FI.Checksum(query)
|
||||
val, err := c.File.Checksum(query)
|
||||
if err == errInvalidOption {
|
||||
return http.StatusBadRequest, err
|
||||
} else if err != nil {
|
||||
|
@ -198,12 +196,12 @@ func renderFile(w http.ResponseWriter, file string, contentType string, c *Reque
|
|||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||
|
||||
var javascript = ""
|
||||
for name := range c.FM.Plugins {
|
||||
for name := range c.Plugins {
|
||||
javascript += plugins[name].JavaScript + "\n"
|
||||
}
|
||||
|
||||
err := tpl.Execute(w, map[string]interface{}{
|
||||
"BaseURL": c.FM.RootURL(),
|
||||
"BaseURL": c.RootURL(),
|
||||
"JavaScript": template.JS(javascript),
|
||||
})
|
||||
if err != nil {
|
||||
|
|
34
package.json
34
package.json
|
@ -14,53 +14,57 @@
|
|||
"moment": "^2.18.1",
|
||||
"normalize.css": "^7.0.0",
|
||||
"vue": "^2.3.3",
|
||||
"vue-i18n": "^7.1.0",
|
||||
"vue-router": "^2.7.0",
|
||||
"vuex": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.2",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"babel-register": "^6.22.0",
|
||||
"chalk": "^1.1.3",
|
||||
"chalk": "^2.0.1",
|
||||
"connect-history-api-fallback": "^1.3.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-friendly-formatter": "^2.0.7",
|
||||
"eslint": "^4.3.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-html": "^2.0.0",
|
||||
"eslint-plugin-html": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.1.1",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"express": "^4.14.1",
|
||||
"extract-text-webpack-plugin": "^2.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-proxy-middleware": "^0.17.3",
|
||||
"opn": "^4.0.2",
|
||||
"optimize-css-assets-webpack-plugin": "^1.3.0",
|
||||
"opn": "^5.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^3.0.0",
|
||||
"ora": "^1.2.0",
|
||||
"rimraf": "^2.6.0",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"sw-precache-webpack-plugin": "^0.9.1",
|
||||
"sw-precache-webpack-plugin": "^0.11.4",
|
||||
"uglify-js": "^3.0.23",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-loader": "^12.1.0",
|
||||
"vue-loader": "^13.0.2",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.3.3",
|
||||
"webpack": "^2.6.1",
|
||||
"webpack": "^3.4.1",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"webpack-dev-middleware": "^1.10.0",
|
||||
"webpack-hot-middleware": "^2.18.0",
|
||||
"webpack-merge": "^4.1.0"
|
||||
"webpack-merge": "^4.1.0",
|
||||
"yml-loader": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
|
|
|
@ -117,7 +117,7 @@ func (h Hugo) undraft(file string) error {
|
|||
type hugo struct{}
|
||||
|
||||
func (h hugo) Before(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
o := c.FM.Plugins["hugo"].(*Hugo)
|
||||
o := c.Plugins["hugo"].(*Hugo)
|
||||
|
||||
// If we are using the 'magic url' for the settings, we should redirect the
|
||||
// request for the acutual path.
|
||||
|
@ -189,7 +189,7 @@ func (h hugo) Before(c *filemanager.RequestContext, w http.ResponseWriter, r *ht
|
|||
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||
|
||||
// Before save command handler.
|
||||
if err := c.FM.Runner("before_publish", filename); err != nil {
|
||||
if err := c.Runner("before_publish", filename); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ func (h hugo) Before(c *filemanager.RequestContext, w http.ResponseWriter, r *ht
|
|||
o.run(false)
|
||||
|
||||
// Executed the before publish command.
|
||||
if err := c.FM.Runner("before_publish", filename); err != nil {
|
||||
if err := c.Runner("before_publish", filename); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
|
|
12
resource.go
12
resource.go
|
@ -34,7 +34,7 @@ func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
case http.MethodPut:
|
||||
// Before save command handler.
|
||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||
if err := c.FM.Runner("before_save", path); err != nil {
|
||||
if err := c.Runner("before_save", path); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// After save command handler.
|
||||
if err := c.FM.Runner("after_save", path); err != nil {
|
||||
if err := c.Runner("after_save", path); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
|
||||
func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Gets the information of the directory/file.
|
||||
f, err := getInfo(r.URL, c.FM, c.User)
|
||||
f, err := getInfo(r.URL, c.FileManager, c.User)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
|||
|
||||
// If it is a dir, go and serve the listing.
|
||||
if f.IsDir {
|
||||
c.FI = f
|
||||
c.File = f
|
||||
return listingHandler(c, w, r)
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
f := c.FI
|
||||
f := c.File
|
||||
f.Kind = "listing"
|
||||
|
||||
// Tries to get the listing data.
|
||||
|
@ -112,7 +112,7 @@ func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (
|
|||
listing := f.listing
|
||||
|
||||
// Defines the cookie scope.
|
||||
cookieScope := c.FM.RootURL()
|
||||
cookieScope := c.RootURL()
|
||||
if cookieScope == "" {
|
||||
cookieScope = "/"
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
49dd472ced00d5e02963554cd0b84393f8c08d75
|
||||
b5a8f3badeeb5ea5e285f23298ddef20ce247376
|
159
settings.go
159
settings.go
|
@ -2,66 +2,18 @@ package filemanager
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
func commandsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return commandsGetHandler(c, w, r)
|
||||
case http.MethodPut:
|
||||
return commandsPutHandler(c, w, r)
|
||||
}
|
||||
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
func commandsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
return renderJSON(w, c.FM.Commands)
|
||||
}
|
||||
|
||||
func commandsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if r.Body == nil {
|
||||
return http.StatusBadGateway, errors.New("Empty request body")
|
||||
}
|
||||
|
||||
var commands map[string][]string
|
||||
|
||||
// Parses the user and checks for error.
|
||||
err := json.NewDecoder(r.Body).Decode(&commands)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, errors.New("Invalid JSON")
|
||||
}
|
||||
|
||||
if err := c.FM.db.Set("config", "commands", commands); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
c.FM.Commands = commands
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func pluginsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return pluginsGetHandler(c, w, r)
|
||||
case http.MethodPut:
|
||||
return pluginsPutHandler(c, w, r)
|
||||
}
|
||||
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
type modifySettingsRequest struct {
|
||||
*modifyRequest
|
||||
Data struct {
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Plugins map[string]map[string]interface{} `json:"plugins"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type pluginOption struct {
|
||||
|
@ -70,19 +22,63 @@ type pluginOption struct {
|
|||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func pluginsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func parsePutSettingsRequest(r *http.Request) (*modifySettingsRequest, error) {
|
||||
// Checks if the request body is empty.
|
||||
if r.Body == nil {
|
||||
return nil, errEmptyRequest
|
||||
}
|
||||
|
||||
// Parses the request body and checks if it's well formed.
|
||||
mod := &modifySettingsRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(mod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Checks if the request type is right.
|
||||
if mod.What != "settings" {
|
||||
return nil, errWrongDataType
|
||||
}
|
||||
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
func settingsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Path != "" && r.URL.Path != "/" {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return settingsGetHandler(c, w, r)
|
||||
case http.MethodPut:
|
||||
return settingsPutHandler(c, w, r)
|
||||
}
|
||||
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
type settingsGetRequest struct {
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Plugins map[string][]pluginOption `json:"plugins"`
|
||||
}
|
||||
|
||||
func settingsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
plugins := map[string][]pluginOption{}
|
||||
result := &settingsGetRequest{
|
||||
Commands: c.Commands,
|
||||
Plugins: map[string][]pluginOption{},
|
||||
}
|
||||
|
||||
for name, p := range c.FM.Plugins {
|
||||
plugins[name] = []pluginOption{}
|
||||
for name, p := range c.Plugins {
|
||||
result.Plugins[name] = []pluginOption{}
|
||||
|
||||
t := reflect.TypeOf(p).Elem()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
plugins[name] = append(plugins[name], pluginOption{
|
||||
result.Plugins[name] = append(result.Plugins[name], pluginOption{
|
||||
Variable: t.Field(i).Name,
|
||||
Name: t.Field(i).Tag.Get("name"),
|
||||
Value: reflect.ValueOf(p).Elem().FieldByName(t.Field(i).Name).Interface(),
|
||||
|
@ -90,37 +86,44 @@ func pluginsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request
|
|||
}
|
||||
}
|
||||
|
||||
return renderJSON(w, plugins)
|
||||
return renderJSON(w, result)
|
||||
}
|
||||
|
||||
func pluginsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func settingsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if r.Body == nil {
|
||||
return http.StatusBadGateway, errors.New("Empty request body")
|
||||
}
|
||||
|
||||
var raw map[string]map[string]interface{}
|
||||
|
||||
// Parses the user and checks for error.
|
||||
err := json.NewDecoder(r.Body).Decode(&raw)
|
||||
mod, err := parsePutSettingsRequest(r)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
for name, plugin := range raw {
|
||||
err = mapstructure.Decode(plugin, c.FM.Plugins[name])
|
||||
if err != nil {
|
||||
// Update the commands.
|
||||
if mod.Which == "commands" {
|
||||
if err := c.db.Set("config", "commands", mod.Data.Commands); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
err = c.FM.db.Set("plugins", name, c.FM.Plugins[name])
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
c.Commands = mod.Data.Commands
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
// Update the plugins.
|
||||
if mod.Which == "plugins" {
|
||||
for name, plugin := range mod.Data.Plugins {
|
||||
err = mapstructure.Decode(plugin, c.Plugins[name])
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
err = c.db.Set("plugins", name, c.Plugins[name])
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
|
214
users.go
214
users.go
|
@ -11,20 +11,22 @@ import (
|
|||
"github.com/asdine/storm"
|
||||
)
|
||||
|
||||
type modifyRequest struct {
|
||||
What string `json:"what"` // Answer to: what data type?
|
||||
Which string `json:"which"` // Answer to: which field?
|
||||
}
|
||||
|
||||
type modifyUserRequest struct {
|
||||
*modifyRequest
|
||||
Data *User `json:"data"`
|
||||
}
|
||||
|
||||
// usersHandler is the entry point of the users API. It's just a router
|
||||
// to send the request to its
|
||||
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Path == "/change-password" {
|
||||
return usersUpdatePassword(c, w, r)
|
||||
}
|
||||
|
||||
if r.URL.Path == "/change-css" {
|
||||
return usersUpdateCSS(c, w, r)
|
||||
}
|
||||
|
||||
// If the user is admin and the HTTP Method is not
|
||||
// PUT, then we return forbidden.
|
||||
if !c.User.Admin {
|
||||
// If the user isn't admin and isn't making a PUT
|
||||
// request, then return forbidden.
|
||||
if !c.User.Admin && r.Method != http.MethodPut {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
|
@ -61,32 +63,38 @@ func getUserID(r *http.Request) (int, error) {
|
|||
// getUser returns the user which is present in the request
|
||||
// body. If the body is empty or the JSON is invalid, it
|
||||
// returns an error.
|
||||
func getUser(r *http.Request) (*User, error) {
|
||||
func getUser(r *http.Request) (*User, string, error) {
|
||||
// Checks if the request body is empty.
|
||||
if r.Body == nil {
|
||||
return nil, errEmptyRequest
|
||||
return nil, "", errEmptyRequest
|
||||
}
|
||||
|
||||
u := &User{}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(u)
|
||||
// Parses the request body and checks if it's well formed.
|
||||
mod := &modifyUserRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(mod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
// Checks if the request type is right.
|
||||
if mod.What != "user" {
|
||||
return nil, "", errWrongDataType
|
||||
}
|
||||
|
||||
return mod.Data, mod.Which, nil
|
||||
}
|
||||
|
||||
func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Request for the default user data.
|
||||
if r.URL.Path == "/base" {
|
||||
return renderJSON(w, c.FM.DefaultUser)
|
||||
return renderJSON(w, c.DefaultUser)
|
||||
}
|
||||
|
||||
// Request for the listing of users.
|
||||
if r.URL.Path == "/" {
|
||||
users := []User{}
|
||||
|
||||
for _, user := range c.FM.Users {
|
||||
for _, user := range c.Users {
|
||||
// Copies the user info and removes its
|
||||
// password so it won't be sent to the
|
||||
// front-end.
|
||||
|
@ -108,7 +116,7 @@ func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// Searches for the user and prints the one who matches.
|
||||
for _, user := range c.FM.Users {
|
||||
for _, user := range c.Users {
|
||||
if user.ID != id {
|
||||
continue
|
||||
}
|
||||
|
@ -127,11 +135,26 @@ func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
u, err := getUser(r)
|
||||
u, _, err := getUser(r)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
// Checks if username isn't empty.
|
||||
if u.Username == "" {
|
||||
return http.StatusBadRequest, errEmptyUsername
|
||||
}
|
||||
|
||||
// Checks if filesystem isn't empty.
|
||||
if u.FileSystem == "" {
|
||||
return http.StatusBadRequest, errEmptyScope
|
||||
}
|
||||
|
||||
// Checks if password isn't empty.
|
||||
if u.Password == "" {
|
||||
return http.StatusBadRequest, errEmptyPassword
|
||||
}
|
||||
|
||||
// The username, password and scope cannot be empty.
|
||||
if u.Username == "" || u.Password == "" || u.FileSystem == "" {
|
||||
return http.StatusBadRequest, errors.New("username, password or scope is empty")
|
||||
|
@ -161,7 +184,7 @@ func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
u.Password = pw
|
||||
|
||||
// Saves the user to the database.
|
||||
err = c.FM.db.Save(u)
|
||||
err = c.db.Save(u)
|
||||
if err == storm.ErrAlreadyExists {
|
||||
return http.StatusConflict, errUserExist
|
||||
}
|
||||
|
@ -171,7 +194,7 @@ func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// Saves the user to the memory.
|
||||
c.FM.Users[u.Username] = u
|
||||
c.Users[u.Username] = u
|
||||
|
||||
// Set the Location header and return.
|
||||
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
|
||||
|
@ -190,7 +213,7 @@ func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
// Deletes the user from the database.
|
||||
err = c.FM.db.DeleteStruct(&User{ID: id})
|
||||
err = c.db.DeleteStruct(&User{ID: id})
|
||||
if err == storm.ErrNotFound {
|
||||
return http.StatusNotFound, errUserNotExist
|
||||
}
|
||||
|
@ -200,9 +223,9 @@ func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
// Delete the user from the in-memory users map.
|
||||
for _, user := range c.FM.Users {
|
||||
for _, user := range c.Users {
|
||||
if user.ID == id {
|
||||
delete(c.FM.Users, user.Username)
|
||||
delete(c.Users, user.Username)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -210,72 +233,79 @@ func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
|||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func usersUpdatePassword(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.Method != http.MethodPut {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
u, err := getUser(r)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
if u.Password == "" {
|
||||
return http.StatusBadRequest, errEmptyPassword
|
||||
}
|
||||
|
||||
pw, err := hashPassword(u.Password)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
c.User.Password = pw
|
||||
err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "Password", pw)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func usersUpdateCSS(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.Method != http.MethodPut {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
u, err := getUser(r)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
c.User.CSS = u.CSS
|
||||
err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "CSS", u.CSS)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// New users should be created on /api/users.
|
||||
if r.URL.Path == "/" {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
// Gets the user ID from the URL and checks if it's valid.
|
||||
id, err := getUserID(r)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
u, err := getUser(r)
|
||||
// Checks if the user has permission to access this page.
|
||||
if !c.User.Admin && id != c.User.ID {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
// Gets the user from the request body.
|
||||
u, which, err := getUser(r)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
// The username and the filesystem cannot be empty.
|
||||
if u.Username == "" || u.FileSystem == "" {
|
||||
return http.StatusBadRequest, errors.New("Username, password or scope are empty")
|
||||
// Updates the CSS and locale.
|
||||
if which == "partial" {
|
||||
c.User.CSS = u.CSS
|
||||
c.User.Locale = u.Locale
|
||||
err = c.db.UpdateField(&User{ID: c.User.ID}, "CSS", u.CSS)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
err = c.db.UpdateField(&User{ID: c.User.ID}, "Locale", u.Locale)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// Updates the Password.
|
||||
if which == "password" {
|
||||
if u.Password == "" {
|
||||
return http.StatusBadRequest, errEmptyPassword
|
||||
}
|
||||
|
||||
pw, err := hashPassword(u.Password)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
c.User.Password = pw
|
||||
err = c.db.UpdateField(&User{ID: c.User.ID}, "Password", pw)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// If can only be all.
|
||||
if which != "all" {
|
||||
return http.StatusBadRequest, errInvalidUpdateField
|
||||
}
|
||||
|
||||
// Checks if username isn't empty.
|
||||
if u.Username == "" {
|
||||
return http.StatusBadRequest, errEmptyUsername
|
||||
}
|
||||
|
||||
// Checks if filesystem isn't empty.
|
||||
if u.FileSystem == "" {
|
||||
return http.StatusBadRequest, errEmptyScope
|
||||
}
|
||||
|
||||
// Initialize rules if they're not initialized.
|
||||
|
@ -288,48 +318,50 @@ func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
|||
u.Commands = []string{}
|
||||
}
|
||||
|
||||
var ouser *User
|
||||
for _, user := range c.FM.Users {
|
||||
// Gets the current saved user from the in-memory map.
|
||||
var suser *User
|
||||
for _, user := range c.Users {
|
||||
if user.ID == id {
|
||||
ouser = user
|
||||
suser = user
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ouser == nil {
|
||||
if suser == nil {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
u.ID = id
|
||||
|
||||
if u.Password == "" {
|
||||
u.Password = ouser.Password
|
||||
} else {
|
||||
// Changes the password if the request wants it.
|
||||
if u.Password != "" {
|
||||
pw, err := hashPassword(u.Password)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
u.Password = pw
|
||||
} else {
|
||||
u.Password = suser.Password
|
||||
}
|
||||
|
||||
// Default permissions if current are nil.
|
||||
if u.Permissions == nil {
|
||||
u.Permissions = c.FM.DefaultUser.Permissions
|
||||
u.Permissions = c.DefaultUser.Permissions
|
||||
}
|
||||
|
||||
// Updates the whole User struct because we always are supposed
|
||||
// to send a new entire object.
|
||||
err = c.FM.db.Save(u)
|
||||
err = c.db.Save(u)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// If the user changed the username, delete the old user
|
||||
// from the in-memory user map.
|
||||
if ouser.Username != u.Username {
|
||||
delete(c.FM.Users, ouser.Username)
|
||||
if suser.Username != u.Username {
|
||||
delete(c.Users, suser.Username)
|
||||
}
|
||||
|
||||
c.FM.Users[u.Username] = u
|
||||
c.Users[u.Username] = u
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue