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 d8bc73390c37409efa60804d94779a7629944caa]] Former-commit-id: 92e99405cbf9935d1cf77b0fe70b122fca552be6 [formerly 3cd365e862f2a54ada60e226a19ac607b8d0c43b] Former-commit-id: cf9815114ac686cdf75a6b1cba15adafe493d083
This commit is contained in:
parent
a5a68a8944
commit
d50bec8caa
|
@ -25,6 +25,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(yml|yaml)$/,
|
||||||
|
loader: 'yml-loader'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.(js|vue)$/,
|
test: /\.(js|vue)$/,
|
||||||
loader: 'eslint-loader',
|
loader: 'eslint-loader',
|
||||||
|
|
|
@ -21,11 +21,10 @@
|
||||||
<!-- Add to home screen for Windows -->
|
<!-- Add to home screen for Windows -->
|
||||||
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
||||||
<meta name="msapplication-TileColor" content="#2979ff">
|
<meta name="msapplication-TileColor" content="#2979ff">
|
||||||
|
<% for (var chunk of webpack.compilation.chunks) {
|
||||||
<% for (var chunk of webpack.chunks) {
|
|
||||||
for (var file of chunk.files) {
|
for (var file of chunk.files) {
|
||||||
if (file.match(/\.(js|css)$/)) { %>
|
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 -->
|
<!-- Plugins info -->
|
||||||
<script>{{ .JavaScript }}</script>
|
<script>{{ .JavaScript }}</script>
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<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>
|
<i class="material-icons">menu</i>
|
||||||
</button>
|
</button>
|
||||||
<img src="../assets/logo.svg" alt="File Manager">
|
<img src="../assets/logo.svg" alt="File Manager">
|
||||||
<search></search>
|
<search></search>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<i class="material-icons">search</i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button v-show="showSaveButton" aria-label="Save" class="action" id="save-button">
|
<button v-show="showSaveButton" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" class="action" id="save-button">
|
||||||
<i class="material-icons" title="Save">save</i>
|
<i class="material-icons">save</i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div v-for="plugin in plugins" :key="plugin.name">
|
<div v-for="plugin in plugins" :key="plugin.name">
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
<i class="material-icons">more_vert</i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -71,9 +71,9 @@
|
||||||
<upload-button v-show="showUpload"></upload-button>
|
<upload-button v-show="showUpload"></upload-button>
|
||||||
<info-button v-show="showCommonButton"></info-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>
|
<i class="material-icons">check_circle</i>
|
||||||
<span>Select</span>
|
<span>{{ $t('buttons.select') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></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 MoveButton from './buttons/Move'
|
||||||
import CopyButton from './buttons/Copy'
|
import CopyButton from './buttons/Copy'
|
||||||
import {mapGetters, mapState} from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
import api from '@/utils/api'
|
import * as api from '@/utils/api'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
|
|
||||||
export default {
|
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>
|
<template>
|
||||||
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
||||||
<div id="input">
|
<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>
|
<i class="material-icons">arrow_back</i>
|
||||||
</button>
|
</button>
|
||||||
<i v-else class="material-icons">search</i>
|
<i v-else class="material-icons">search</i>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
ref="input"
|
ref="input"
|
||||||
:autofocus="active"
|
:autofocus="active"
|
||||||
v-model.trim="value"
|
v-model.trim="value"
|
||||||
aria-label="Write here to search"
|
:aria-label="$t('search.writeToSearch')"
|
||||||
:placeholder="placeholder">
|
:placeholder="placeholder">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -78,10 +78,10 @@ export default {
|
||||||
// Placeholder value.
|
// Placeholder value.
|
||||||
placeholder: function () {
|
placeholder: function () {
|
||||||
if (this.user.allowCommands && this.user.commands.length > 0) {
|
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
|
// The text that is shown on the results' box while
|
||||||
// there is no search result or command output to show.
|
// there is no search result or command output to show.
|
||||||
|
@ -92,16 +92,16 @@ export default {
|
||||||
|
|
||||||
if (this.value.length === 0) {
|
if (this.value.length === 0) {
|
||||||
if (this.user.allowCommands && this.user.commands.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) {
|
if (!this.supported() || !this.user.allowCommands) {
|
||||||
return 'Press enter to search.'
|
return this.$t('search.pressToSearch')
|
||||||
} else {
|
} else {
|
||||||
return 'Press enter to execute.'
|
return this.$t('search.pressToExecute')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<nav :class="{active}">
|
<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>
|
<i class="material-icons">folder</i>
|
||||||
<span>My Files</span>
|
<span>{{ $t('sidebar.myFiles') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div v-if="user.allowNew">
|
<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>
|
<i class="material-icons">create_new_folder</i>
|
||||||
<span>New folder</span>
|
<span>{{ $t('sidebar.newFolder') }}</span>
|
||||||
</button>
|
</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>
|
<i class="material-icons">note_add</i>
|
||||||
<span>New file</span>
|
<span>{{ $t('sidebar.newFile') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -25,21 +25,21 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
<i class="material-icons">settings_applications</i>
|
||||||
<span>Settings</span>
|
<span>{{ $t('sidebar.settings') }}</span>
|
||||||
</router-link>
|
</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>
|
<i class="material-icons">exit_to_app</i>
|
||||||
<span>Logout</span>
|
<span>{{ $t('sidebar.logout') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="credits">
|
<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 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>
|
</p>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">content_copy</i>
|
||||||
<span>Copy file</span>
|
<span>{{ $t('buttons.copyFile') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">delete</i>
|
||||||
<span>Delete</span>
|
<span>{{ $t('buttons.delete') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">file_download</i>
|
||||||
<span>Download</span>
|
<span>{{ $t('buttons.download') }}</span>
|
||||||
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">info</i>
|
||||||
<span>Info</span>
|
<span>{{ $t('buttons.info') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">forward</i>
|
||||||
<span>Move file</span>
|
<span>{{ $t('buttons.moveFile') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">mode_edit</i>
|
||||||
<span>Rename</span>
|
<span>{{ $t('buttons.rename') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">{{ icon() }}</i>
|
||||||
<span>Switch view</span>
|
<span>{{ $t('buttons.switchView') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<i class="material-icons">file_upload</i>
|
||||||
<span>Upload</span>
|
<span>{{ $t('buttons.upload') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<form id="editor" :class="req.language">
|
<form id="editor" :class="req.language">
|
||||||
<div v-if="hasMetadata" id="metadata">
|
<div v-if="hasMetadata" id="metadata">
|
||||||
<h2>Metadata</h2>
|
<h2>{{ $t('files.metadata') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 v-if="hasMetadata">Body</h2>
|
<h2 v-if="hasMetadata">{{ $t('files.body') }}</h2>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -123,7 +123,3 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -2,9 +2,9 @@
|
||||||
<div v-if="(req.numDirs + req.numFiles) == 0">
|
<div v-if="(req.numDirs + req.numFiles) == 0">
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">sentiment_dissatisfied</i>
|
<i class="material-icons">sentiment_dissatisfied</i>
|
||||||
<span>It feels lonely here...</span>
|
<span>{{ $t('files.lonely') }}</span>
|
||||||
</h2>
|
</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>
|
||||||
<div v-else id="listing"
|
<div v-else id="listing"
|
||||||
:class="req.display"
|
:class="req.display"
|
||||||
|
@ -16,23 +16,23 @@
|
||||||
<div></div>
|
<div></div>
|
||||||
<div>
|
<div>
|
||||||
<p :class="{ active: nameSorted }" class="name" @click="sort('name')">
|
<p :class="{ active: nameSorted }" class="name" @click="sort('name')">
|
||||||
<span>Name</span>
|
<span>{{ $t('files.name') }}</span>
|
||||||
<i class="material-icons">{{ nameIcon }}</i>
|
<i class="material-icons">{{ nameIcon }}</i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p :class="{ active: sizeSorted }" class="size" @click="sort('size')">
|
<p :class="{ active: sizeSorted }" class="size" @click="sort('size')">
|
||||||
<span>Size</span>
|
<span>{{ $t('files.size') }}</span>
|
||||||
<i class="material-icons">{{ sizeIcon }}</i>
|
<i class="material-icons">{{ sizeIcon }}</i>
|
||||||
</p>
|
</p>
|
||||||
<p :class="{ active: modifiedSorted }" class="modified" @click="sort('modified')">
|
<p :class="{ active: modifiedSorted }" class="modified" @click="sort('modified')">
|
||||||
<span>Last modified</span>
|
<span>{{ $t('files.lastModified') }}</span>
|
||||||
<i class="material-icons">{{ modifiedIcon }}</i>
|
<i class="material-icons">{{ modifiedIcon }}</i>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div v-if="req.numDirs > 0">
|
||||||
<item v-for="(item, index) in req.items"
|
<item v-for="(item, index) in req.items"
|
||||||
v-if="item.isDir"
|
v-if="item.isDir"
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</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">
|
<div v-if="req.numFiles > 0">
|
||||||
<item v-for="(item, index) in req.items"
|
<item v-for="(item, index) in req.items"
|
||||||
v-if="!item.isDir"
|
v-if="!item.isDir"
|
||||||
|
@ -62,12 +62,12 @@
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</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">
|
<div v-show="$store.state.multiple" :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||||
<p>Multiple selection enabled</p>
|
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" title="Clear" aria-label="Clear" class="action">
|
<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" title="Clear">clear</i>
|
<i class="material-icons">clear</i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="previewer">
|
<div id="previewer">
|
||||||
<div class="bar">
|
<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>
|
<i class="material-icons">close</i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -11,8 +11,12 @@
|
||||||
<info-button></info-button>
|
<info-button></info-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="action" @click="prev" v-show="hasPrevious"><i class="material-icons">chevron_left</i></button>
|
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
|
||||||
<button class="action" @click="next" v-show="hasNext"><i class="material-icons">chevron_right</i></button>
|
<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">
|
<div class="preview">
|
||||||
<img v-if="req.type == 'image'" :src="raw()">
|
<img v-if="req.type == 'image'" :src="raw()">
|
||||||
|
@ -24,7 +28,7 @@
|
||||||
</video>
|
</video>
|
||||||
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw()"></object>
|
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw()"></object>
|
||||||
<a v-else-if="req.type == 'blob'" :href="download()">
|
<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>
|
</a>
|
||||||
<pre v-else >{{ req.content }}</pre>
|
<pre v-else >{{ req.content }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,10 +39,10 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import url from '@/utils/url'
|
import url from '@/utils/url'
|
||||||
import api from '@/utils/api'
|
import api from '@/utils/api'
|
||||||
import InfoButton from './buttons/Info'
|
import InfoButton from '@/components/buttons/Info'
|
||||||
import DeleteButton from './buttons/Delete'
|
import DeleteButton from '@/components/buttons/Delete'
|
||||||
import RenameButton from './buttons/Rename'
|
import RenameButton from '@/components/buttons/Rename'
|
||||||
import DownloadButton from './buttons/Download'
|
import DownloadButton from '@/components/buttons/Download'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'preview',
|
name: 'preview',
|
|
@ -1,13 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<h3>Copy</h3>
|
<h3>{{ $t('prompts.copy') }}</h3>
|
||||||
<p>Choose the place to copy your files:</p>
|
<p>{{ $t('prompts.copyMessage') }}</p>
|
||||||
|
|
||||||
<file-list @update:selected="val => dest = val"></file-list>
|
<file-list @update:selected="val => dest = val"></file-list>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="ok" @click="copy">Copy</button>
|
<button class="ok" @click="copy">{{ $t('buttons.copy') }}</button>
|
||||||
<button class="cancel" @click="$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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<h3>Delete files</h3>
|
<h3>{{ $t('prompts.deleteTitle') }}</h3>
|
||||||
<p v-show="req.kind !== 'listing'">Are you sure you want to delete this file/folder?</p>
|
<p v-show="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
|
||||||
<p v-show="req.kind === 'listing'">Are you sure you want to delete {{ selectedCount }} file(s)?</p>
|
<p v-show="req.kind === 'listing'">{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
|
||||||
<div>
|
<div>
|
||||||
<button @click="submit" autofocus>Delete</button>
|
<button @click="submit" autofocus>{{ $t('buttons.delete') }}</button>
|
||||||
<button @click="closeHovers" class="cancel">Cancel</button>
|
<button class="cancel"
|
||||||
|
@click="$store.commit('closeHovers')"
|
||||||
|
:aria-label="$t('buttons.cancel')"
|
||||||
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt" id="download">
|
<div class="prompt" id="download">
|
||||||
<h3>Download files</h3>
|
<h3>{{ $t('prompts.download') }}</h3>
|
||||||
<p>Choose the format you want to download.</p>
|
<p>{{ $t('prompts.downloadMessage') }}</p>
|
||||||
|
|
||||||
<button @click="download('zip')" autofocus>zip</button>
|
<button @click="download('zip')" autofocus>zip</button>
|
||||||
<button @click="download('tar')" autofocus>tar</button>
|
<button @click="download('tar')" autofocus>tar</button>
|
||||||
<button @click="download('targz')" autofocus>tar.gz</button>
|
<button @click="download('targz')" autofocus>tar.gz</button>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt error">
|
<div class="prompt error">
|
||||||
<i class="material-icons">error_outline</i>
|
<i class="material-icons">error_outline</i>
|
||||||
<h3>Something went wrong</h3>
|
<h3>{{ $t('prompts.error') }}</h3>
|
||||||
<pre>{{ $store.state.showMessage }}</pre>
|
<pre>{{ $store.state.showMessage }}</pre>
|
||||||
<div>
|
<div>
|
||||||
<button @click="close" autofocus>Close</button>
|
<button @click="close" autofocus>{{ $t('buttons.close') }}</button>
|
||||||
<button @click="reportIssue" class="cancel">Report Issue</button>
|
<button @click="reportIssue" class="cancel">{{ $t('buttons.reportIssue') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
:data-url="item.url">{{ item.name }}</li>
|
:data-url="item.url">{{ item.name }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Currently navigating on: <code>{{ nav }}</code>.</p>
|
<p>{{ $t('prompts.currentlyNavigating') }} <code>{{ nav }}</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt help">
|
<div class="prompt help">
|
||||||
<h3>Help</h3>
|
<h3>{{ $t('help.help') }}</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>F1</strong> - this information</li>
|
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
|
||||||
<li><strong>F2</strong> - rename file</li>
|
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
|
||||||
<li><strong>DEL</strong> - delete selected items</li>
|
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
|
||||||
<li><strong>ESC</strong> - clear selection and/or close the prompt</li>
|
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
|
||||||
<li><strong>CTRL + S</strong> - save a file or download the directory where you are</li>
|
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
|
||||||
<li><strong>CTRL + Click</strong> - select multiple files or directories</li>
|
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
|
||||||
<li><strong>Double click</strong> - open a file or directory</li>
|
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
|
||||||
<li><strong>Click</strong> - select file or directory</li>
|
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
|
||||||
</ul>
|
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
|
||||||
|
|
||||||
<p>Not available yet</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><strong>Alt + Click</strong> - select a group of files</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<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 v-show="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name() }}</p>
|
||||||
<p><strong>Size:</strong> <span id="content_length"></span>{{ humanSize() }}</p>
|
<p><strong>{{ $t('prompts.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.lastModified') }}:</strong> {{ humanTime() }}</p>
|
||||||
|
|
||||||
<section v-show="dir() && selected.length === 0">
|
<section v-show="dir() && selected.length === 0">
|
||||||
<p><strong>Number of files:</strong> {{ req.numFiles }}</p>
|
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
|
||||||
<p><strong>Number of directories:</strong> {{ req.numDirs }}</p>
|
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-show="!dir()">
|
<section v-show="!dir()">
|
||||||
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">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')">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')">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')">show</a></code></p>
|
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<h3>Move</h3>
|
<h3>{{ $t('prompts.move') }}</h3>
|
||||||
<p>Choose new house for your file(s)/folder(s):</p>
|
<p>{{ $t('prompts.moveMessage') }}</p>
|
||||||
|
|
||||||
<file-list @update:selected="val => dest = val"></file-list>
|
<file-list @update:selected="val => dest = val"></file-list>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="ok" @click="move">Move</button>
|
<button class="ok" @click="move">{{ $t('buttons.move') }}</button>
|
||||||
<button class="cancel" @click="$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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<h3>New directory</h3>
|
<h3>{{ $t('prompts.newDir') }}</h3>
|
||||||
<p>Write the name of the new directory.</p>
|
<p>{{ $t('prompts.newDirMessage') }}</p>
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
<div>
|
<div>
|
||||||
<button class="ok" @click="submit">Create</button>
|
<button class="ok" @click="submit">{{ $t('buttons.create') }}</button>
|
||||||
<button class="cancel" @click="$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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<h3>New file</h3>
|
<h3>{{ $t('prompts.newFile') }}</h3>
|
||||||
<p>Write the name of the new file.</p>
|
<p>{{ $t('prompts.newFileMessage') }}</p>
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
<div>
|
<div>
|
||||||
<button class="ok" @click="submit">Create</button>
|
<button class="ok" @click="submit">{{ $t('buttons.create') }}</button>
|
||||||
<button class="cancel" @click="$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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -27,7 +27,10 @@
|
||||||
:placeholder="input.placeholder">
|
:placeholder="input.placeholder">
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" class="ok" :value="prompt.ok">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<h3>Rename</h3>
|
<h3>{{ $t('prompts.rename') }}</h3>
|
||||||
<p>Insert a new name for <code>{{ oldName() }}</code>:</p>
|
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
||||||
|
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
<div>
|
<div>
|
||||||
<button @click="submit" type="submit">Rename</button>
|
<button @click="submit" type="submit">{{ $t('buttons.rename') }}</button>
|
||||||
<button @click="cancel" class="cancel">Cancel</button>
|
<button class="cancel"
|
||||||
|
@click="$store.commit('closeHovers')"
|
||||||
|
:aria-label="$t('buttons.cancel')"
|
||||||
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<i class="material-icons">done</i>
|
<i class="material-icons">done</i>
|
||||||
<h3>{{ $store.state.showMessage }}</h3>
|
<h3>{{ $store.state.showMessage }}</h3>
|
||||||
<div>
|
<div>
|
||||||
<button @click="close" autofocus>OK</button>
|
<button @click="close" autofocus>{{ $t('buttons.ok') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
width: 1em
|
width: 1em
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard > *:first-child {
|
.dashboard > h1:first-of-type {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ form.dashboard > p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard select,
|
||||||
.dashboard textarea,
|
.dashboard textarea,
|
||||||
.dashboard input[type="text"],
|
.dashboard input[type="text"],
|
||||||
.dashboard input[type="password"] {
|
.dashboard input[type="password"] {
|
||||||
|
@ -60,12 +61,18 @@ form.dashboard > p:last-child {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard #locale,
|
||||||
.dashboard #username,
|
.dashboard #username,
|
||||||
.dashboard #password,
|
.dashboard #password,
|
||||||
.dashboard #scope {
|
.dashboard #scope {
|
||||||
max-width: 18em;
|
max-width: 18em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard #locale {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
margin-top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard textarea:focus,
|
.dashboard textarea:focus,
|
||||||
.dashboard textarea:hover,
|
.dashboard textarea:hover,
|
||||||
.dashboard input[type="text"]:focus,
|
.dashboard input[type="text"]:focus,
|
||||||
|
@ -118,3 +125,27 @@ p code {
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
line-height: 1.5;
|
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 "./listing.css";
|
||||||
@import "./editor.css";
|
@import "./editor.css";
|
||||||
@import "./dashboard.css";
|
@import "./dashboard.css";
|
||||||
|
@import "./login.css";
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * *
|
||||||
* ACTION *
|
* 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 App from './App'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import i18n from './i18n'
|
||||||
|
|
||||||
Vue.config.productionTip = true
|
Vue.config.productionTip = true
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
store,
|
store,
|
||||||
router,
|
router,
|
||||||
|
i18n,
|
||||||
template: '<App/>',
|
template: '<App/>',
|
||||||
components: { App }
|
components: { App }
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
import Login from '@/components/Login'
|
import Login from '@/views/Login'
|
||||||
import Main from '@/components/Main'
|
import Layout from '@/views/Layout'
|
||||||
import Files from '@/components/Files'
|
import Files from '@/views/Files'
|
||||||
import Users from '@/components/Users'
|
import Users from '@/views/Users'
|
||||||
import User from '@/components/User'
|
import User from '@/views/User'
|
||||||
import GlobalSettings from '@/components/GlobalSettings'
|
import GlobalSettings from '@/views/GlobalSettings'
|
||||||
import ProfileSettings from '@/components/ProfileSettings'
|
import ProfileSettings from '@/views/ProfileSettings'
|
||||||
import error403 from '@/components/errors/403'
|
import Error403 from '@/views/errors/403'
|
||||||
import error404 from '@/components/errors/404'
|
import Error404 from '@/views/errors/404'
|
||||||
import error500 from '@/components/errors/500'
|
import Error500 from '@/views/errors/500'
|
||||||
import auth from '@/utils/auth.js'
|
import auth from '@/utils/auth.js'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|
||||||
|
@ -34,15 +34,9 @@ const router = new Router({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
redirect: {
|
|
||||||
path: '/files/'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/*',
|
path: '/*',
|
||||||
component: Main,
|
component: Layout,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
},
|
},
|
||||||
|
@ -75,17 +69,17 @@ const router = new Router({
|
||||||
{
|
{
|
||||||
path: '/403',
|
path: '/403',
|
||||||
name: 'Forbidden',
|
name: 'Forbidden',
|
||||||
component: error403
|
component: Error403
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: '/404',
|
||||||
name: 'Not Found',
|
name: 'Not Found',
|
||||||
component: error404
|
component: Error404
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/500',
|
path: '/500',
|
||||||
name: 'Internal Server Error',
|
name: 'Internal Server Error',
|
||||||
component: error500
|
component: Error500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/users',
|
path: '/users',
|
||||||
|
@ -95,12 +89,6 @@ const router = new Router({
|
||||||
requiresAdmin: true
|
requiresAdmin: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/users/',
|
|
||||||
redirect: {
|
|
||||||
path: '/users'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/users/*',
|
path: '/users/*',
|
||||||
name: 'User',
|
name: 'User',
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import i18n from '@/i18n'
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
closeHovers: state => {
|
closeHovers: state => {
|
||||||
state.show = null
|
state.show = null
|
||||||
|
@ -22,8 +24,10 @@ const mutations = {
|
||||||
},
|
},
|
||||||
setLoading: (state, value) => { state.loading = value },
|
setLoading: (state, value) => { state.loading = value },
|
||||||
setReload: (state, value) => { state.reload = value },
|
setReload: (state, value) => { state.reload = value },
|
||||||
setUser: (state, value) => (state.user = value),
|
setUser: (state, value) => {
|
||||||
setUserCSS: (state, value) => (state.user.css = value),
|
i18n.locale = value.locale
|
||||||
|
state.user = value
|
||||||
|
},
|
||||||
setJWT: (state, value) => (state.jwt = value),
|
setJWT: (state, value) => (state.jwt = value),
|
||||||
multiple: (state, value) => (state.multiple = value),
|
multiple: (state, value) => (state.multiple = value),
|
||||||
addSelected: (state, value) => (state.selected.push(value)),
|
addSelected: (state, value) => (state.selected.push(value)),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import store from '@/store'
|
||||||
|
|
||||||
const ssl = (window.location.protocol === 'https:')
|
const ssl = (window.location.protocol === 'https:')
|
||||||
|
|
||||||
function removePrefix (url) {
|
export function removePrefix (url) {
|
||||||
if (url.startsWith('/files')) {
|
if (url.startsWith('/files')) {
|
||||||
return url.slice(6)
|
return url.slice(6)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ function removePrefix (url) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetch (url) {
|
export function fetch (url) {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -24,10 +24,7 @@ function fetch (url) {
|
||||||
resolve(JSON.parse(request.responseText))
|
resolve(JSON.parse(request.responseText))
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
reject({
|
reject(new Error(request.status))
|
||||||
message: request.responseText,
|
|
||||||
status: request.status
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +33,7 @@ function fetch (url) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function rm (url) {
|
export function rm (url) {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -57,7 +54,7 @@ function rm (url) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function post (url, content = '') {
|
export function post (url, content = '') {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
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)
|
url = removePrefix(url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -132,15 +129,15 @@ function moveCopy (items, copy = false) {
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
function move (items) {
|
export function move (items) {
|
||||||
return moveCopy(items)
|
return moveCopy(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
function copy (items) {
|
export function copy (items) {
|
||||||
return moveCopy(items, true)
|
return moveCopy(items, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checksum (url, algo) {
|
export function checksum (url, algo) {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
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:')
|
let protocol = (ssl ? 'wss:' : 'ws:')
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
url = `${protocol}//${window.location.host}${store.state.baseURL}/api/command${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
|
conn.onclose = onclose
|
||||||
}
|
}
|
||||||
|
|
||||||
function search (url, search, onmessage, onclose) {
|
export function search (url, search, onmessage, onclose) {
|
||||||
let protocol = (ssl ? 'wss:' : 'ws:')
|
let protocol = (ssl ? 'wss:' : 'ws:')
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
url = `${protocol}//${window.location.host}${store.state.baseURL}/api/search${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
|
conn.onclose = onclose
|
||||||
}
|
}
|
||||||
|
|
||||||
function download (format, ...files) {
|
export function download (format, ...files) {
|
||||||
let url = `${store.state.baseURL}/api/download`
|
let url = `${store.state.baseURL}/api/download`
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
|
@ -206,7 +203,59 @@ function download (format, ...files) {
|
||||||
window.open(url)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('GET', `${store.state.baseURL}/api/users/`, true)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('GET', `${store.state.baseURL}/api/users/${id}`, true)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('POST', `${store.state.baseURL}/api/users/`, true)
|
request.open('POST', `${store.state.baseURL}/api/users/`, true)
|
||||||
|
@ -265,11 +314,15 @@ function newUser (user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.onerror = (error) => reject(error)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('PUT', `${store.state.baseURL}/api/users/${user.ID}`, true)
|
request.open('PUT', `${store.state.baseURL}/api/users/${user.ID}`, true)
|
||||||
|
@ -286,11 +339,15 @@ function updateUser (user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.onerror = (error) => reject(error)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('DELETE', `${store.state.baseURL}/api/users/${id}`, true)
|
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 {
|
export default {
|
||||||
|
removePrefix,
|
||||||
delete: rm,
|
delete: rm,
|
||||||
fetch,
|
fetch,
|
||||||
checksum,
|
checksum,
|
||||||
|
@ -448,16 +380,13 @@ export default {
|
||||||
command,
|
command,
|
||||||
search,
|
search,
|
||||||
download,
|
download,
|
||||||
getUser,
|
// other things
|
||||||
|
getSettings,
|
||||||
|
updateSettings,
|
||||||
|
// User things
|
||||||
newUser,
|
newUser,
|
||||||
updateUser,
|
getUser,
|
||||||
getUsers,
|
getUsers,
|
||||||
updatePassword,
|
updateUser,
|
||||||
updateCSS,
|
|
||||||
getCommands,
|
|
||||||
updateCommands,
|
|
||||||
removePrefix,
|
|
||||||
getPlugins,
|
|
||||||
updatePlugins,
|
|
||||||
deleteUser
|
deleteUser
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@ function loggedIn () {
|
||||||
parseToken(request.responseText)
|
parseToken(request.responseText)
|
||||||
resolve()
|
resolve()
|
||||||
} else {
|
} else {
|
||||||
reject()
|
reject(new Error(request.responseText))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.onerror = () => reject()
|
request.onerror = () => reject(new Error('Could not finish the request'))
|
||||||
request.send()
|
request.send()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ function login (user, password) {
|
||||||
reject(request.responseText)
|
reject(request.responseText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.onerror = () => reject()
|
request.onerror = () => reject(new Error('Could not finish the request'))
|
||||||
request.send(JSON.stringify(data))
|
request.send(JSON.stringify(data))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div id="breadcrumbs">
|
<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>
|
<i class="material-icons">home</i>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error">
|
<div v-if="error">
|
||||||
<not-found v-if="error === 404"></not-found>
|
<not-found v-if="error.message === '404'"></not-found>
|
||||||
<forbidden v-else-if="error === 403"></forbidden>
|
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||||
<internal-error v-else></internal-error>
|
<internal-error v-else></internal-error>
|
||||||
</div>
|
</div>
|
||||||
<editor v-else-if="isEditor"></editor>
|
<editor v-else-if="isEditor"></editor>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
<preview v-else-if="isPreview"></preview>
|
<preview v-else-if="isPreview"></preview>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<span>Loading...</span>
|
<span>{{ $t('files.loading') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,9 +30,9 @@
|
||||||
import Forbidden from './errors/403'
|
import Forbidden from './errors/403'
|
||||||
import NotFound from './errors/404'
|
import NotFound from './errors/404'
|
||||||
import InternalError from './errors/500'
|
import InternalError from './errors/500'
|
||||||
import Preview from './Preview'
|
import Preview from '@/components/files/Preview'
|
||||||
import Listing from './Listing'
|
import Listing from '@/components/files/Listing'
|
||||||
import Editor from './Editor'
|
import Editor from '@/components/files/Editor'
|
||||||
import api from '@/utils/api'
|
import api from '@/utils/api'
|
||||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||||
|
|
||||||
|
@ -116,20 +116,11 @@ export default {
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
window.addEventListener('keydown', this.keyEvent)
|
window.addEventListener('keydown', this.keyEvent)
|
||||||
window.addEventListener('scroll', event => {
|
window.addEventListener('scroll', this.scroll)
|
||||||
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'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.keyEvent)
|
window.removeEventListener('keydown', this.keyEvent)
|
||||||
|
window.removeEventListener('scroll', this.scroll)
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
this.$store.commit('updateRequest', {})
|
this.$store.commit('updateRequest', {})
|
||||||
|
@ -163,12 +154,6 @@ export default {
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setLoading(false)
|
this.setLoading(false)
|
||||||
|
|
||||||
if (typeof error === 'object') {
|
|
||||||
this.error = error.status
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.error = error
|
this.error = error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -220,11 +205,21 @@ export default {
|
||||||
|
|
||||||
if (this.req.kind !== 'editor') {
|
if (this.req.kind !== 'editor') {
|
||||||
document.getElementById('download-button').click()
|
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 () {
|
openSidebar () {
|
||||||
this.$store.commit('showHover', 'sidebar')
|
this.$store.commit('showHover', 'sidebar')
|
||||||
},
|
},
|
|
@ -1,12 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<h1>Global Settings</h1>
|
<ul id="nav">
|
||||||
|
<li>
|
||||||
<ul>
|
<router-link to="/settings/profile">
|
||||||
<li><router-link to="/settings/profile">Go to Profile Settings</router-link></li>
|
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.profileSettings') }}
|
||||||
<li><router-link to="/users">Go to User Management</router-link></li>
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link to="/users">
|
||||||
|
{{ $t('settings.userManagement') }} <i class="material-icons">keyboard_arrow_right</i>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h1>{{ $t('settings.globalSettings') }}</h1>
|
||||||
|
|
||||||
<form @submit="savePlugin" v-if="plugins.length > 0">
|
<form @submit="savePlugin" v-if="plugins.length > 0">
|
||||||
<template v-for="plugin in plugins">
|
<template v-for="plugin in plugins">
|
||||||
<h2>{{ capitalize(plugin.name) }}</h2>
|
<h2>{{ capitalize(plugin.name) }}</h2>
|
||||||
|
@ -23,11 +31,9 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form @submit="saveCommands">
|
<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
|
<p class="small">{{ $t('settings.commandsHelp') }}</p>
|
||||||
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>
|
|
||||||
|
|
||||||
<template v-for="command in commands">
|
<template v-for="command in commands">
|
||||||
<h3>{{ capitalize(command.name) }}</h3>
|
<h3>{{ capitalize(command.name) }}</h3>
|
||||||
|
@ -42,7 +48,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapMutations } from 'vuex'
|
import { mapState, mapMutations } from 'vuex'
|
||||||
import api from '@/utils/api'
|
import { getSettings, updateSettings } from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
|
@ -56,24 +62,20 @@ export default {
|
||||||
...mapState([ 'user' ])
|
...mapState([ 'user' ])
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
api.getCommands()
|
getSettings()
|
||||||
.then(commands => {
|
.then(settings => {
|
||||||
for (let key in commands) {
|
for (let key in settings.plugins) {
|
||||||
|
this.plugins.push(this.parsePlugin(key, settings.plugins[key]))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in settings.commands) {
|
||||||
this.commands.push({
|
this.commands.push({
|
||||||
name: key,
|
name: key,
|
||||||
value: commands[key].join('\n')
|
value: settings.commands[key].join('\n')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => { this.showError(error) })
|
.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: {
|
methods: {
|
||||||
...mapMutations([ 'showSuccess', 'showError' ]),
|
...mapMutations([ 'showSuccess', 'showError' ]),
|
||||||
|
@ -102,8 +104,8 @@ export default {
|
||||||
commands[command.name] = value
|
commands[command.name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
api.updateCommands(commands)
|
updateSettings(commands, 'commands')
|
||||||
.then(() => { this.showSuccess('Commands updated!') })
|
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
|
||||||
.catch(error => { this.showError(error) })
|
.catch(error => { this.showError(error) })
|
||||||
},
|
},
|
||||||
savePlugin (event) {
|
savePlugin (event) {
|
||||||
|
@ -129,10 +131,8 @@ export default {
|
||||||
plugins[plugin.name] = p
|
plugins[plugin.name] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(plugins)
|
updateSettings(plugins, 'plugins')
|
||||||
|
.then(() => { this.showSuccess(this.$t('settings.pluginsUpdated')) })
|
||||||
api.updatePlugins(plugins)
|
|
||||||
.then(() => { this.showSuccess('Plugins settings updated!') })
|
|
||||||
.catch(error => { this.showError(error) })
|
.catch(error => { this.showError(error) })
|
||||||
},
|
},
|
||||||
parsePlugin (name, plugin) {
|
parsePlugin (name, plugin) {
|
|
@ -10,13 +10,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Search from './Search'
|
import Search from '@/components/Search'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from '@/components/Sidebar'
|
||||||
import Prompts from './prompts/Prompts'
|
import Prompts from '@/components/prompts/Prompts'
|
||||||
import SiteHeader from './Header'
|
import SiteHeader from '@/components/Header'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'main',
|
name: 'layout',
|
||||||
components: {
|
components: {
|
||||||
Search,
|
Search,
|
||||||
Sidebar,
|
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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<form @submit="save" class="dashboard">
|
<form @submit="save" class="dashboard">
|
||||||
<h1 v-if="id === 0">New User</h1>
|
<h1 v-if="id === 0">{{ $t('settings.newUser') }}</h1>
|
||||||
<h1 v-else>User {{ username }}</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="username">{{ $t('settings.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="password">{{ $t('settings.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="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.
|
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
||||||
If you select "Administrator", all of the other options will be automatically checked.
|
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
||||||
The management of users remains a privilege of an administrator.</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><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 v-for="(value, key) in permissions" :key="key">
|
<p v-for="(value, key) in permissions" :key="key">
|
||||||
<input type="checkbox" :disabled="admin" v-model="permissions[key]"> {{ capitalize(key) }}
|
<input type="checkbox" :disabled="admin" v-model="permissions[key]"> {{ capitalize(key) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Commands</h3>
|
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||||
|
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||||
<p class="small">A space separated list with the available commands for this user. Example: <i>git svn hg</i>.</p>
|
|
||||||
|
|
||||||
<input type="text" v-model.trim="commands">
|
<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
|
<p class="small">{{ $t('settings.rulesHelp1') }}</p>
|
||||||
show up in the listings and they won't be accessible to the user. We support regex and paths relative to
|
|
||||||
the user's scope.</p>
|
|
||||||
|
|
||||||
<p class="small">Each rule goes in one different line and must start with the keyword <code>allow</code> or <code>disallow</code>.
|
<i18n path="settings.rulesHelp2" tag="p" class="small">
|
||||||
Then you should write <code>regex</code> if you are using a regular expression and then the expression or the path.</p>
|
<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">
|
<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 regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</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 /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<textarea v-model.trim="rules"></textarea>
|
<textarea v-model.trim="rules"></textarea>
|
||||||
|
|
||||||
<h2>Custom Stylesheet</h2>
|
<h2>{{ $t('settings.customStylesheet') }}</h2>
|
||||||
|
|
||||||
<textarea name="css"></textarea>
|
<textarea name="css"></textarea>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="delete">Delete</button>
|
<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="Save">
|
<input type="submit" :value="$t('buttons.save')">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -60,8 +58,13 @@
|
||||||
<h3>Delete User</h3>
|
<h3>Delete User</h3>
|
||||||
<p>Are you sure you want to delete this user?</p>
|
<p>Are you sure you want to delete this user?</p>
|
||||||
<div>
|
<div>
|
||||||
<button @click="deleteUser" autofocus>Delete</button>
|
<button @click="deleteUser" autofocus>{{ $t('buttons.delete') }}</button>
|
||||||
<button @click="closeHovers" class="cancel">Cancel</button>
|
<button class="cancel"
|
||||||
|
@click="closeHovers"
|
||||||
|
:aria-label="$t('buttons.cancel')"
|
||||||
|
:title="$t('buttons.cancel')">
|
||||||
|
{{ $t('buttons.cancel') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,10 +72,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations } from 'vuex'
|
import { mapMutations } from 'vuex'
|
||||||
import api from '@/utils/api'
|
import { getUser, newUser, updateUser, deleteUser } from '@/utils/api'
|
||||||
|
import Languages from '@/components/Languages'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'user',
|
name: 'user',
|
||||||
|
components: { Languages },
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
@ -85,6 +90,7 @@ export default {
|
||||||
username: '',
|
username: '',
|
||||||
filesystem: '',
|
filesystem: '',
|
||||||
rules: '',
|
rules: '',
|
||||||
|
locale: '',
|
||||||
css: '',
|
css: '',
|
||||||
commands: ''
|
commands: ''
|
||||||
}
|
}
|
||||||
|
@ -92,7 +98,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
passwordPlaceholder () {
|
passwordPlaceholder () {
|
||||||
if (this.$route.path === '/users/new') return ''
|
if (this.$route.path === '/users/new') return ''
|
||||||
return '(leave blank to avoid changes)'
|
return this.$t('settings.avoidChanges')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
@ -119,7 +125,7 @@ export default {
|
||||||
user = 'base'
|
user = 'base'
|
||||||
}
|
}
|
||||||
|
|
||||||
api.getUser(user).then(user => {
|
getUser(user).then(user => {
|
||||||
this.id = user.ID
|
this.id = user.ID
|
||||||
this.admin = user.admin
|
this.admin = user.admin
|
||||||
this.allowCommands = user.allowCommands
|
this.allowCommands = user.allowCommands
|
||||||
|
@ -130,6 +136,7 @@ export default {
|
||||||
this.commands = user.commands.join(' ')
|
this.commands = user.commands.join(' ')
|
||||||
this.css = user.css
|
this.css = user.css
|
||||||
this.permissions = user.permissions
|
this.permissions = user.permissions
|
||||||
|
this.locale = user.locale
|
||||||
|
|
||||||
for (let rule of user.rules) {
|
for (let rule of user.rules) {
|
||||||
if (rule.allow) {
|
if (rule.allow) {
|
||||||
|
@ -173,6 +180,7 @@ export default {
|
||||||
this.username = ''
|
this.username = ''
|
||||||
this.filesystem = ''
|
this.filesystem = ''
|
||||||
this.rules = ''
|
this.rules = ''
|
||||||
|
this.locale = ''
|
||||||
this.css = ''
|
this.css = ''
|
||||||
this.commands = ''
|
this.commands = ''
|
||||||
},
|
},
|
||||||
|
@ -182,9 +190,9 @@ export default {
|
||||||
deleteUser (event) {
|
deleteUser (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
api.deleteUser(this.id).then(location => {
|
deleteUser(this.id).then(location => {
|
||||||
this.$router.push({ path: '/users' })
|
this.$router.push({ path: '/users' })
|
||||||
this.$store.commit('showSuccess', 'User deleted!')
|
this.$store.commit('showSuccess', this.$t('settings.userDeleted'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$store.commit('showError', e)
|
this.$store.commit('showError', e)
|
||||||
})
|
})
|
||||||
|
@ -194,9 +202,9 @@ export default {
|
||||||
let user = this.parseForm()
|
let user = this.parseForm()
|
||||||
|
|
||||||
if (this.$route.path === '/users/new') {
|
if (this.$route.path === '/users/new') {
|
||||||
api.newUser(user).then(location => {
|
newUser(user).then(location => {
|
||||||
this.$router.push({ path: location })
|
this.$router.push({ path: location })
|
||||||
this.$store.commit('showSuccess', 'User created!')
|
this.$store.commit('showSuccess', this.$t('settings.userCreated'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$store.commit('showError', e)
|
this.$store.commit('showError', e)
|
||||||
})
|
})
|
||||||
|
@ -204,8 +212,12 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
api.updateUser(user).then(location => {
|
updateUser(user).then(location => {
|
||||||
this.$store.commit('showSuccess', 'User updated!')
|
if (user.ID === this.$store.state.user.ID) {
|
||||||
|
this.$store.commit('setUser', user)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.commit('showSuccess', this.$t('settings.userUpdated'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$store.commit('showError', e)
|
this.$store.commit('showError', e)
|
||||||
})
|
})
|
||||||
|
@ -222,6 +234,7 @@ export default {
|
||||||
allowEdit: this.allowEdit,
|
allowEdit: this.allowEdit,
|
||||||
permissions: this.permissions,
|
permissions: this.permissions,
|
||||||
css: this.css,
|
css: this.css,
|
||||||
|
locale: this.locale,
|
||||||
commands: this.commands.split(' '),
|
commands: this.commands.split(' '),
|
||||||
rules: []
|
rules: []
|
||||||
}
|
}
|
||||||
|
@ -269,7 +282,3 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<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>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Username</th>
|
<th>{{ $t('settings.username') }}</th>
|
||||||
<th>Admin</th>
|
<th>{{ $t('settings.admin') }}</th>
|
||||||
<th>Scope</th>
|
<th>{{ $t('settings.scope') }}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">error</i>
|
<i class="material-icons">error</i>
|
||||||
<span>You're not welcome here.</span>
|
<span>{{ $t('errors.forbidden') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">gps_off</i>
|
<i class="material-icons">gps_off</i>
|
||||||
<span>This location can't be reached.</span>
|
<span>{{ $t('errors.notFound') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">error_outline</i>
|
<i class="material-icons">error_outline</i>
|
||||||
<span>Something really went wrong.</span>
|
<span>{{ $t('errors.internal') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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.
|
// Checks if the user exists.
|
||||||
u, ok := c.FM.Users[cred.Username]
|
u, ok := c.Users[cred.Username]
|
||||||
if !ok {
|
if !ok {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ func printToken(c *RequestContext, w http.ResponseWriter) (int, error) {
|
||||||
|
|
||||||
// Creates the token and signs it.
|
// Creates the token and signs it.
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
string, err := token.SignedString(c.FM.key)
|
string, err := token.SignedString(c.key)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
|
@ -114,7 +114,7 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
||||||
// User if it is valid.
|
// User if it is valid.
|
||||||
func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
||||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||||
return c.FM.key, nil
|
return c.key, nil
|
||||||
}
|
}
|
||||||
var claims claims
|
var claims claims
|
||||||
token, err := request.ParseFromRequestWithClaims(r,
|
token, err := request.ParseFromRequestWithClaims(r,
|
||||||
|
@ -127,7 +127,7 @@ func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, ok := c.FM.Users[claims.User.Username]
|
u, ok := c.Users[claims.User.Username]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ var (
|
||||||
commands string
|
commands string
|
||||||
logfile string
|
logfile string
|
||||||
plugin string
|
plugin string
|
||||||
|
locale string
|
||||||
port int
|
port int
|
||||||
allowCommands bool
|
allowCommands bool
|
||||||
allowEdit bool
|
allowEdit bool
|
||||||
|
@ -47,6 +48,7 @@ func init() {
|
||||||
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
|
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(&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.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.StringVar(&plugin, "plugin", "", "Plugin you want to enable")
|
||||||
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
||||||
}
|
}
|
||||||
|
@ -62,6 +64,7 @@ func setupViper() {
|
||||||
viper.SetDefault("AllowEdit", true)
|
viper.SetDefault("AllowEdit", true)
|
||||||
viper.SetDefault("AllowNew", true)
|
viper.SetDefault("AllowNew", true)
|
||||||
viper.SetDefault("Plugin", "")
|
viper.SetDefault("Plugin", "")
|
||||||
|
viper.SetDefault("Locale", "en")
|
||||||
|
|
||||||
viper.BindPFlag("Port", flag.Lookup("port"))
|
viper.BindPFlag("Port", flag.Lookup("port"))
|
||||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||||
|
@ -72,6 +75,7 @@ func setupViper() {
|
||||||
viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
|
viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
|
||||||
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
||||||
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
||||||
|
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
||||||
viper.BindPFlag("Plugin", flag.Lookup("plugin"))
|
viper.BindPFlag("Plugin", flag.Lookup("plugin"))
|
||||||
|
|
||||||
viper.SetConfigName("filemanager")
|
viper.SetConfigName("filemanager")
|
||||||
|
@ -133,6 +137,7 @@ func main() {
|
||||||
AllowNew: viper.GetBool("AllowNew"),
|
AllowNew: viper.GetBool("AllowNew"),
|
||||||
Commands: viper.GetStringSlice("Commands"),
|
Commands: viper.GetStringSlice("Commands"),
|
||||||
Rules: []*filemanager.Rule{},
|
Rules: []*filemanager.Rule{},
|
||||||
|
Locale: viper.GetString("Locale"),
|
||||||
CSS: "",
|
CSS: "",
|
||||||
FileSystem: fileutils.Dir(viper.GetString("Scope")),
|
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
|
// If the file isn't a directory, serve it using http.ServeFile. We display it
|
||||||
// inline if it is requested.
|
// inline if it is requested.
|
||||||
if !c.FI.IsDir {
|
if !c.File.IsDir {
|
||||||
if r.URL.Query().Get("inline") == "true" {
|
if r.URL.Query().Get("inline") == "true" {
|
||||||
w.Header().Set("Content-Disposition", "inline")
|
w.Header().Set("Content-Disposition", "inline")
|
||||||
} else {
|
} 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
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,10 +46,10 @@ func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
// Clean the slashes.
|
// Clean the slashes.
|
||||||
name = fileutils.SlashClean(name)
|
name = fileutils.SlashClean(name)
|
||||||
files = append(files, filepath.Join(c.FI.Path, name))
|
files = append(files, filepath.Join(c.File.Path, name))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
files = append(files, c.FI.Path)
|
files = append(files, c.File.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the format is true, just set it to "zip".
|
// 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.
|
// Defines the file name.
|
||||||
name := c.FI.Name
|
name := c.File.Name
|
||||||
if name == "." || name == "" {
|
if name == "." || name == "" {
|
||||||
name = "download"
|
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 {
|
func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||||
// Gets the directory information using the Virtual File System of
|
// Gets the directory information using the Virtual File System of
|
||||||
// the user configuration.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,10 @@ var (
|
||||||
errUserNotExist = errors.New("user does not exist")
|
errUserNotExist = errors.New("user does not exist")
|
||||||
errEmptyRequest = errors.New("request body is empty")
|
errEmptyRequest = errors.New("request body is empty")
|
||||||
errEmptyPassword = errors.New("password 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{}
|
plugins = map[string]Plugin{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -139,6 +143,9 @@ type User struct {
|
||||||
// Custom styles for this user.
|
// Custom styles for this user.
|
||||||
CSS string `json:"css"`
|
CSS string `json:"css"`
|
||||||
|
|
||||||
|
// Locale is the language of the user.
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
|
||||||
// These indicate if the user can perform certain actions.
|
// These indicate if the user can perform certain actions.
|
||||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
AllowNew bool `json:"allowNew"` // Create files and folders
|
||||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
||||||
|
@ -208,6 +215,7 @@ var DefaultUser = User{
|
||||||
Rules: []*Rule{},
|
Rules: []*Rule{},
|
||||||
CSS: "",
|
CSS: "",
|
||||||
Admin: true,
|
Admin: true,
|
||||||
|
Locale: "en",
|
||||||
FileSystem: fileutils.Dir("."),
|
FileSystem: fileutils.Dir("."),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,23 +436,25 @@ func (m *FileManager) registerPermission(name string, value bool) error {
|
||||||
// Compatible with http.Handler.
|
// Compatible with http.Handler.
|
||||||
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
code, err := serveHTTP(&RequestContext{
|
code, err := serveHTTP(&RequestContext{
|
||||||
FM: m,
|
FileManager: m,
|
||||||
User: nil,
|
User: nil,
|
||||||
FI: nil,
|
File: nil,
|
||||||
}, w, r)
|
}, w, r)
|
||||||
|
|
||||||
if code != 0 {
|
if code >= 400 {
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
|
|
||||||
if err != nil {
|
if err == nil {
|
||||||
log.Print(err)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
} else {
|
|
||||||
txt := http.StatusText(code)
|
txt := http.StatusText(code)
|
||||||
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
||||||
w.Write([]byte(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.
|
// 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.
|
// RequestContext contains the needed information to make handlers work.
|
||||||
type RequestContext struct {
|
type RequestContext struct {
|
||||||
|
*FileManager
|
||||||
User *User
|
User *User
|
||||||
FM *FileManager
|
File *file
|
||||||
FI *file
|
|
||||||
// On API handlers, Router is the APi handler we want.
|
// On API handlers, Router is the APi handler we want.
|
||||||
Router string
|
Router string
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,9 @@ type RequestContext struct {
|
||||||
func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
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
|
// 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!
|
// 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
|
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" {
|
if r.URL.Path == "/sw.js" {
|
||||||
return renderFile(
|
return renderFile(
|
||||||
w,
|
w,
|
||||||
c.FM.assets.MustString("sw.js"),
|
c.assets.MustString("sw.js"),
|
||||||
"application/javascript",
|
"application/javascript",
|
||||||
c,
|
c,
|
||||||
)
|
)
|
||||||
|
@ -65,7 +65,7 @@ func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
|
|
||||||
return renderFile(
|
return renderFile(
|
||||||
w,
|
w,
|
||||||
c.FM.assets.MustString("index.html"),
|
c.assets.MustString("index.html"),
|
||||||
"text/html",
|
"text/html",
|
||||||
c,
|
c,
|
||||||
)
|
)
|
||||||
|
@ -74,13 +74,13 @@ func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
// staticHandler handles the static assets path.
|
// staticHandler handles the static assets path.
|
||||||
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path != "/static/manifest.json" {
|
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 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderFile(
|
return renderFile(
|
||||||
w,
|
w,
|
||||||
c.FM.assets.MustString("static/manifest.json"),
|
c.assets.MustString("static/manifest.json"),
|
||||||
"application/json",
|
"application/json",
|
||||||
c,
|
c,
|
||||||
)
|
)
|
||||||
|
@ -107,7 +107,7 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for p := range c.FM.Plugins {
|
for p := range c.Plugins {
|
||||||
code, err := plugins[p].Handler.Before(c, w, r)
|
code, err := plugins[p].Handler.Before(c, w, r)
|
||||||
if code != 0 || err != nil {
|
if code != 0 || err != nil {
|
||||||
return code, err
|
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" {
|
if c.Router == "checksum" || c.Router == "download" {
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
return errorToHTTP(err, false), err
|
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)
|
code, err = resourceHandler(c, w, r)
|
||||||
case "users":
|
case "users":
|
||||||
code, err = usersHandler(c, w, r)
|
code, err = usersHandler(c, w, r)
|
||||||
case "commands":
|
case "settings":
|
||||||
code, err = commandsHandler(c, w, r)
|
code, err = settingsHandler(c, w, r)
|
||||||
case "plugins":
|
|
||||||
code, err = pluginsHandler(c, w, r)
|
|
||||||
default:
|
default:
|
||||||
code = http.StatusNotFound
|
code = http.StatusNotFound
|
||||||
}
|
}
|
||||||
|
@ -150,7 +148,7 @@ func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int,
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for p := range c.FM.Plugins {
|
for p := range c.Plugins {
|
||||||
code, err := plugins[p].Handler.After(c, w, r)
|
code, err := plugins[p].Handler.After(c, w, r)
|
||||||
if code != 0 || err != nil {
|
if code != 0 || err != nil {
|
||||||
return code, err
|
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) {
|
func checksumHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
query := r.URL.Query().Get("algo")
|
query := r.URL.Query().Get("algo")
|
||||||
|
|
||||||
val, err := c.FI.Checksum(query)
|
val, err := c.File.Checksum(query)
|
||||||
if err == errInvalidOption {
|
if err == errInvalidOption {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
} else if err != nil {
|
} 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")
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||||
|
|
||||||
var javascript = ""
|
var javascript = ""
|
||||||
for name := range c.FM.Plugins {
|
for name := range c.Plugins {
|
||||||
javascript += plugins[name].JavaScript + "\n"
|
javascript += plugins[name].JavaScript + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tpl.Execute(w, map[string]interface{}{
|
err := tpl.Execute(w, map[string]interface{}{
|
||||||
"BaseURL": c.FM.RootURL(),
|
"BaseURL": c.RootURL(),
|
||||||
"JavaScript": template.JS(javascript),
|
"JavaScript": template.JS(javascript),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
34
package.json
34
package.json
|
@ -14,53 +14,57 @@
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
"normalize.css": "^7.0.0",
|
"normalize.css": "^7.0.0",
|
||||||
"vue": "^2.3.3",
|
"vue": "^2.3.3",
|
||||||
|
"vue-i18n": "^7.1.0",
|
||||||
"vue-router": "^2.7.0",
|
"vue-router": "^2.7.0",
|
||||||
"vuex": "^2.3.1"
|
"vuex": "^2.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.7.2",
|
"autoprefixer": "^7.1.2",
|
||||||
"babel-core": "^6.22.1",
|
"babel-core": "^6.22.1",
|
||||||
"babel-eslint": "^7.1.1",
|
"babel-eslint": "^7.1.1",
|
||||||
"babel-loader": "^6.2.10",
|
"babel-loader": "^7.1.1",
|
||||||
"babel-plugin-transform-runtime": "^6.22.0",
|
"babel-plugin-transform-runtime": "^6.22.0",
|
||||||
"babel-preset-env": "^1.3.2",
|
"babel-preset-env": "^1.3.2",
|
||||||
"babel-preset-stage-2": "^6.22.0",
|
"babel-preset-stage-2": "^6.22.0",
|
||||||
"babel-register": "^6.22.0",
|
"babel-register": "^6.22.0",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^2.0.1",
|
||||||
"connect-history-api-fallback": "^1.3.0",
|
"connect-history-api-fallback": "^1.3.0",
|
||||||
"copy-webpack-plugin": "^4.0.1",
|
"copy-webpack-plugin": "^4.0.1",
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.0",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^4.3.0",
|
||||||
"eslint-config-standard": "^6.2.1",
|
"eslint-config-standard": "^10.2.1",
|
||||||
"eslint-friendly-formatter": "^2.0.7",
|
"eslint-friendly-formatter": "^3.0.0",
|
||||||
"eslint-loader": "^1.7.1",
|
"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-promise": "^3.4.0",
|
||||||
"eslint-plugin-standard": "^2.0.1",
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"express": "^4.14.1",
|
"express": "^4.14.1",
|
||||||
"extract-text-webpack-plugin": "^2.0.0",
|
"extract-text-webpack-plugin": "^3.0.0",
|
||||||
"file-loader": "^0.11.1",
|
"file-loader": "^0.11.1",
|
||||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||||
"html-webpack-plugin": "^2.28.0",
|
"html-webpack-plugin": "^2.28.0",
|
||||||
"http-proxy-middleware": "^0.17.3",
|
"http-proxy-middleware": "^0.17.3",
|
||||||
"opn": "^4.0.2",
|
"opn": "^5.1.0",
|
||||||
"optimize-css-assets-webpack-plugin": "^1.3.0",
|
"optimize-css-assets-webpack-plugin": "^3.0.0",
|
||||||
"ora": "^1.2.0",
|
"ora": "^1.2.0",
|
||||||
"rimraf": "^2.6.0",
|
"rimraf": "^2.6.0",
|
||||||
"semver": "^5.3.0",
|
"semver": "^5.3.0",
|
||||||
"shelljs": "^0.7.6",
|
"shelljs": "^0.7.6",
|
||||||
"sw-precache-webpack-plugin": "^0.9.1",
|
"sw-precache-webpack-plugin": "^0.11.4",
|
||||||
"uglify-js": "^3.0.23",
|
"uglify-js": "^3.0.23",
|
||||||
"url-loader": "^0.5.8",
|
"url-loader": "^0.5.8",
|
||||||
"vue-loader": "^12.1.0",
|
"vue-loader": "^13.0.2",
|
||||||
"vue-style-loader": "^3.0.1",
|
"vue-style-loader": "^3.0.1",
|
||||||
"vue-template-compiler": "^2.3.3",
|
"vue-template-compiler": "^2.3.3",
|
||||||
"webpack": "^2.6.1",
|
"webpack": "^3.4.1",
|
||||||
"webpack-bundle-analyzer": "^2.2.1",
|
"webpack-bundle-analyzer": "^2.2.1",
|
||||||
"webpack-dev-middleware": "^1.10.0",
|
"webpack-dev-middleware": "^1.10.0",
|
||||||
"webpack-hot-middleware": "^2.18.0",
|
"webpack-hot-middleware": "^2.18.0",
|
||||||
"webpack-merge": "^4.1.0"
|
"webpack-merge": "^4.1.0",
|
||||||
|
"yml-loader": "^2.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4.0.0",
|
"node": ">= 4.0.0",
|
||||||
|
|
|
@ -117,7 +117,7 @@ func (h Hugo) undraft(file string) error {
|
||||||
type hugo struct{}
|
type hugo struct{}
|
||||||
|
|
||||||
func (h hugo) Before(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
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
|
// If we are using the 'magic url' for the settings, we should redirect the
|
||||||
// request for the acutual path.
|
// 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)
|
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||||
|
|
||||||
// Before save command handler.
|
// 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
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ func (h hugo) Before(c *filemanager.RequestContext, w http.ResponseWriter, r *ht
|
||||||
o.run(false)
|
o.run(false)
|
||||||
|
|
||||||
// Executed the before publish command.
|
// 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
|
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:
|
case http.MethodPut:
|
||||||
// Before save command handler.
|
// Before save command handler.
|
||||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
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
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// After save command handler.
|
// 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
|
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) {
|
func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Gets the information of the directory/file.
|
// 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 {
|
if err != nil {
|
||||||
return errorToHTTP(err, false), err
|
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 it is a dir, go and serve the listing.
|
||||||
if f.IsDir {
|
if f.IsDir {
|
||||||
c.FI = f
|
c.File = f
|
||||||
return listingHandler(c, w, r)
|
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) {
|
func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
f := c.FI
|
f := c.File
|
||||||
f.Kind = "listing"
|
f.Kind = "listing"
|
||||||
|
|
||||||
// Tries to get the listing data.
|
// Tries to get the listing data.
|
||||||
|
@ -112,7 +112,7 @@ func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (
|
||||||
listing := f.listing
|
listing := f.listing
|
||||||
|
|
||||||
// Defines the cookie scope.
|
// Defines the cookie scope.
|
||||||
cookieScope := c.FM.RootURL()
|
cookieScope := c.RootURL()
|
||||||
if cookieScope == "" {
|
if cookieScope == "" {
|
||||||
cookieScope = "/"
|
cookieScope = "/"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
49dd472ced00d5e02963554cd0b84393f8c08d75
|
b5a8f3badeeb5ea5e285f23298ddef20ce247376
|
147
settings.go
147
settings.go
|
@ -2,66 +2,18 @@ package filemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
func commandsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
type modifySettingsRequest struct {
|
||||||
switch r.Method {
|
*modifyRequest
|
||||||
case http.MethodGet:
|
Data struct {
|
||||||
return commandsGetHandler(c, w, r)
|
Commands map[string][]string `json:"commands"`
|
||||||
case http.MethodPut:
|
Plugins map[string]map[string]interface{} `json:"plugins"`
|
||||||
return commandsPutHandler(c, w, r)
|
} `json:"data"`
|
||||||
}
|
|
||||||
|
|
||||||
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 pluginOption struct {
|
type pluginOption struct {
|
||||||
|
@ -70,19 +22,63 @@ type pluginOption struct {
|
||||||
Value interface{} `json:"value"`
|
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 {
|
if !c.User.Admin {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins := map[string][]pluginOption{}
|
result := &settingsGetRequest{
|
||||||
|
Commands: c.Commands,
|
||||||
|
Plugins: map[string][]pluginOption{},
|
||||||
|
}
|
||||||
|
|
||||||
for name, p := range c.FM.Plugins {
|
for name, p := range c.Plugins {
|
||||||
plugins[name] = []pluginOption{}
|
result.Plugins[name] = []pluginOption{}
|
||||||
|
|
||||||
t := reflect.TypeOf(p).Elem()
|
t := reflect.TypeOf(p).Elem()
|
||||||
for i := 0; i < t.NumField(); i++ {
|
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,
|
Variable: t.Field(i).Name,
|
||||||
Name: t.Field(i).Tag.Get("name"),
|
Name: t.Field(i).Tag.Get("name"),
|
||||||
Value: reflect.ValueOf(p).Elem().FieldByName(t.Field(i).Name).Interface(),
|
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 {
|
if !c.User.Admin {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Body == nil {
|
mod, err := parsePutSettingsRequest(r)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
}
|
}
|
||||||
|
// Update the commands.
|
||||||
|
if mod.Which == "commands" {
|
||||||
|
if err := c.db.Set("config", "commands", mod.Data.Commands); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
for name, plugin := range raw {
|
c.Commands = mod.Data.Commands
|
||||||
err = mapstructure.Decode(plugin, c.FM.Plugins[name])
|
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 {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.FM.db.Set("plugins", name, c.FM.Plugins[name])
|
err = c.db.Set("plugins", name, c.Plugins[name])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
|
214
users.go
214
users.go
|
@ -11,20 +11,22 @@ import (
|
||||||
"github.com/asdine/storm"
|
"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
|
// usersHandler is the entry point of the users API. It's just a router
|
||||||
// to send the request to its
|
// to send the request to its
|
||||||
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path == "/change-password" {
|
// If the user isn't admin and isn't making a PUT
|
||||||
return usersUpdatePassword(c, w, r)
|
// request, then return forbidden.
|
||||||
}
|
if !c.User.Admin && r.Method != http.MethodPut {
|
||||||
|
|
||||||
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 {
|
|
||||||
return http.StatusForbidden, nil
|
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
|
// getUser returns the user which is present in the request
|
||||||
// body. If the body is empty or the JSON is invalid, it
|
// body. If the body is empty or the JSON is invalid, it
|
||||||
// returns an error.
|
// 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 {
|
if r.Body == nil {
|
||||||
return nil, errEmptyRequest
|
return nil, "", errEmptyRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
u := &User{}
|
// Parses the request body and checks if it's well formed.
|
||||||
|
mod := &modifyUserRequest{}
|
||||||
err := json.NewDecoder(r.Body).Decode(u)
|
err := json.NewDecoder(r.Body).Decode(mod)
|
||||||
if err != nil {
|
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) {
|
func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Request for the default user data.
|
// Request for the default user data.
|
||||||
if r.URL.Path == "/base" {
|
if r.URL.Path == "/base" {
|
||||||
return renderJSON(w, c.FM.DefaultUser)
|
return renderJSON(w, c.DefaultUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request for the listing of users.
|
// Request for the listing of users.
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
users := []User{}
|
users := []User{}
|
||||||
|
|
||||||
for _, user := range c.FM.Users {
|
for _, user := range c.Users {
|
||||||
// Copies the user info and removes its
|
// Copies the user info and removes its
|
||||||
// password so it won't be sent to the
|
// password so it won't be sent to the
|
||||||
// front-end.
|
// 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.
|
// 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 {
|
if user.ID != id {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -127,11 +135,26 @@ func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := getUser(r)
|
u, _, err := getUser(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
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.
|
// The username, password and scope cannot be empty.
|
||||||
if u.Username == "" || u.Password == "" || u.FileSystem == "" {
|
if u.Username == "" || u.Password == "" || u.FileSystem == "" {
|
||||||
return http.StatusBadRequest, errors.New("username, password or scope is empty")
|
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
|
u.Password = pw
|
||||||
|
|
||||||
// Saves the user to the database.
|
// Saves the user to the database.
|
||||||
err = c.FM.db.Save(u)
|
err = c.db.Save(u)
|
||||||
if err == storm.ErrAlreadyExists {
|
if err == storm.ErrAlreadyExists {
|
||||||
return http.StatusConflict, errUserExist
|
return http.StatusConflict, errUserExist
|
||||||
}
|
}
|
||||||
|
@ -171,7 +194,7 @@ func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves the user to the memory.
|
// Saves the user to the memory.
|
||||||
c.FM.Users[u.Username] = u
|
c.Users[u.Username] = u
|
||||||
|
|
||||||
// Set the Location header and return.
|
// Set the Location header and return.
|
||||||
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
|
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.
|
// 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 {
|
if err == storm.ErrNotFound {
|
||||||
return http.StatusNotFound, errUserNotExist
|
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.
|
// Delete the user from the in-memory users map.
|
||||||
for _, user := range c.FM.Users {
|
for _, user := range c.Users {
|
||||||
if user.ID == id {
|
if user.ID == id {
|
||||||
delete(c.FM.Users, user.Username)
|
delete(c.Users, user.Username)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,72 +233,79 @@ func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Reques
|
||||||
return http.StatusOK, nil
|
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) {
|
func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// New users should be created on /api/users.
|
// New users should be created on /api/users.
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets the user ID from the URL and checks if it's valid.
|
||||||
id, err := getUserID(r)
|
id, err := getUserID(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
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 {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The username and the filesystem cannot be empty.
|
// Updates the CSS and locale.
|
||||||
if u.Username == "" || u.FileSystem == "" {
|
if which == "partial" {
|
||||||
return http.StatusBadRequest, errors.New("Username, password or scope are empty")
|
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.
|
// Initialize rules if they're not initialized.
|
||||||
|
@ -288,48 +318,50 @@ func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request)
|
||||||
u.Commands = []string{}
|
u.Commands = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ouser *User
|
// Gets the current saved user from the in-memory map.
|
||||||
for _, user := range c.FM.Users {
|
var suser *User
|
||||||
|
for _, user := range c.Users {
|
||||||
if user.ID == id {
|
if user.ID == id {
|
||||||
ouser = user
|
suser = user
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if suser == nil {
|
||||||
if ouser == nil {
|
|
||||||
return http.StatusNotFound, nil
|
return http.StatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u.ID = id
|
u.ID = id
|
||||||
|
|
||||||
if u.Password == "" {
|
// Changes the password if the request wants it.
|
||||||
u.Password = ouser.Password
|
if u.Password != "" {
|
||||||
} else {
|
|
||||||
pw, err := hashPassword(u.Password)
|
pw, err := hashPassword(u.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Password = pw
|
u.Password = pw
|
||||||
|
} else {
|
||||||
|
u.Password = suser.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default permissions if current are nil.
|
||||||
if u.Permissions == 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
|
// Updates the whole User struct because we always are supposed
|
||||||
// to send a new entire object.
|
// to send a new entire object.
|
||||||
err = c.FM.db.Save(u)
|
err = c.db.Save(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user changed the username, delete the old user
|
// If the user changed the username, delete the old user
|
||||||
// from the in-memory user map.
|
// from the in-memory user map.
|
||||||
if ouser.Username != u.Username {
|
if suser.Username != u.Username {
|
||||||
delete(c.FM.Users, ouser.Username)
|
delete(c.Users, suser.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.FM.Users[u.Username] = u
|
c.Users[u.Username] = u
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue