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:
231
assets/src/views/Files.vue
Normal file
231
assets/src/views/Files.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="breadcrumbs">
|
||||
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</router-link>
|
||||
|
||||
<span v-for="link in breadcrumbs" :key="link.name">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<router-link :to="link.url">{{ link.name }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="error">
|
||||
<not-found v-if="error.message === '404'"></not-found>
|
||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||
<internal-error v-else></internal-error>
|
||||
</div>
|
||||
<editor v-else-if="isEditor"></editor>
|
||||
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||
<preview v-else-if="isPreview"></preview>
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Forbidden from './errors/403'
|
||||
import NotFound from './errors/404'
|
||||
import InternalError from './errors/500'
|
||||
import Preview from '@/components/files/Preview'
|
||||
import Listing from '@/components/files/Listing'
|
||||
import Editor from '@/components/files/Editor'
|
||||
import api from '@/utils/api'
|
||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'files',
|
||||
components: {
|
||||
Forbidden,
|
||||
NotFound,
|
||||
InternalError,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'selectedCount'
|
||||
]),
|
||||
...mapState([
|
||||
'req',
|
||||
'user',
|
||||
'reload',
|
||||
'multiple',
|
||||
'loading'
|
||||
]),
|
||||
isListing () {
|
||||
return this.req.kind === 'listing' && !this.loading
|
||||
},
|
||||
isPreview () {
|
||||
return this.req.kind === 'preview' && !this.loading
|
||||
},
|
||||
isEditor () {
|
||||
return this.req.kind === 'editor' && !this.loading
|
||||
},
|
||||
breadcrumbs () {
|
||||
let parts = this.$route.path.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
}
|
||||
}
|
||||
|
||||
breadcrumbs.shift()
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
'reload': function () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('scroll', this.scroll)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('scroll', this.scroll)
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'setLoading' ]),
|
||||
fetchData () {
|
||||
// Reset view information.
|
||||
this.$store.commit('setReload', false)
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true)
|
||||
this.error = null
|
||||
|
||||
let url = this.$route.path
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
api.fetch(url)
|
||||
.then((req) => {
|
||||
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||
}
|
||||
|
||||
this.$store.commit('updateRequest', req)
|
||||
document.title = req.name
|
||||
this.setLoading(false)
|
||||
})
|
||||
.catch(error => {
|
||||
this.setLoading(false)
|
||||
this.error = error
|
||||
})
|
||||
},
|
||||
keyEvent (event) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// If we're on a listing, unselect all
|
||||
// files and folders.
|
||||
if (this.req.kind === 'listing') {
|
||||
this.$store.commit('resetSelected')
|
||||
}
|
||||
}
|
||||
|
||||
// Del!
|
||||
if (event.keyCode === 46) {
|
||||
if (this.req.kind === 'editor' ||
|
||||
this.$route.name !== 'Files' ||
|
||||
this.loading ||
|
||||
!this.user.allowEdit ||
|
||||
(this.req.kind === 'listing' && this.selectedCount === 0)) return
|
||||
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault()
|
||||
this.$store.commit('showHover', 'help')
|
||||
}
|
||||
|
||||
// F2!
|
||||
if (event.keyCode === 113) {
|
||||
if (this.req.kind === 'editor' ||
|
||||
this.$route.name !== 'Files' ||
|
||||
this.loading ||
|
||||
!this.user.allowEdit ||
|
||||
(this.req.kind === 'listing' && this.selectedCount === 0) ||
|
||||
(this.req.kind === 'listing' && this.selectedCount > 1)) return
|
||||
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
|
||||
// CTRL + S
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.req.kind !== 'editor') {
|
||||
document.getElementById('download-button').click()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll (event) {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return
|
||||
|
||||
let top = 112 - window.scrollY
|
||||
|
||||
if (top < 64) {
|
||||
top = 64
|
||||
}
|
||||
|
||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||
},
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openSearch () {
|
||||
this.$store.commit('showHover', 'search')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
177
assets/src/views/GlobalSettings.vue
Normal file
177
assets/src/views/GlobalSettings.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<ul id="nav">
|
||||
<li>
|
||||
<router-link to="/settings/profile">
|
||||
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.profileSettings') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/users">
|
||||
{{ $t('settings.userManagement') }} <i class="material-icons">keyboard_arrow_right</i>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>{{ $t('settings.globalSettings') }}</h1>
|
||||
|
||||
<form @submit="savePlugin" v-if="plugins.length > 0">
|
||||
<template v-for="plugin in plugins">
|
||||
<h2>{{ capitalize(plugin.name) }}</h2>
|
||||
|
||||
<p v-for="field in plugin.fields" :key="field.variable">
|
||||
<label v-if="field.type !== 'checkbox'">{{ field.name }}</label>
|
||||
<input v-if="field.type === 'text'" type="text" v-model.trim="field.value">
|
||||
<input v-else-if="field.type === 'checkbox'" type="checkbox" v-model.trim="field.value">
|
||||
<template v-if="field.type === 'checkbox'">{{ capitalize(field.name, 'caps') }}</template>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<p><input type="submit" value="Save"></p>
|
||||
</form>
|
||||
|
||||
<form @submit="saveCommands">
|
||||
<h2>{{ $t('settings.commands') }}</h2>
|
||||
|
||||
<p class="small">{{ $t('settings.commandsHelp') }}</p>
|
||||
|
||||
<template v-for="command in commands">
|
||||
<h3>{{ capitalize(command.name) }}</h3>
|
||||
<textarea v-model.trim="command.value"></textarea>
|
||||
</template>
|
||||
|
||||
<p><input type="submit" value="Save"></p>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { getSettings, updateSettings } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
data: function () {
|
||||
return {
|
||||
commands: [],
|
||||
plugins: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ])
|
||||
},
|
||||
created () {
|
||||
getSettings()
|
||||
.then(settings => {
|
||||
for (let key in settings.plugins) {
|
||||
this.plugins.push(this.parsePlugin(key, settings.plugins[key]))
|
||||
}
|
||||
|
||||
for (let key in settings.commands) {
|
||||
this.commands.push({
|
||||
name: key,
|
||||
value: settings.commands[key].join('\n')
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(error => { this.showError(error) })
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showSuccess', 'showError' ]),
|
||||
capitalize (name, where = '_') {
|
||||
if (where === 'caps') where = /(?=[A-Z])/
|
||||
let splitted = name.split(where)
|
||||
name = ''
|
||||
|
||||
for (let i = 0; i < splitted.length; i++) {
|
||||
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + ' '
|
||||
}
|
||||
|
||||
return name.slice(0, -1)
|
||||
},
|
||||
saveCommands (event) {
|
||||
event.preventDefault()
|
||||
|
||||
let commands = {}
|
||||
|
||||
for (let command of this.commands) {
|
||||
let value = command.value.split('\n')
|
||||
if (value.length === 1 && value[0] === '') {
|
||||
value = []
|
||||
}
|
||||
|
||||
commands[command.name] = value
|
||||
}
|
||||
|
||||
updateSettings(commands, 'commands')
|
||||
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
|
||||
.catch(error => { this.showError(error) })
|
||||
},
|
||||
savePlugin (event) {
|
||||
event.preventDefault()
|
||||
let plugins = {}
|
||||
|
||||
for (let plugin of this.plugins) {
|
||||
let p = {}
|
||||
|
||||
for (let field of plugin.fields) {
|
||||
p[field.variable] = field.value
|
||||
|
||||
if (field.original === 'array') {
|
||||
let val = field.value.split(' ')
|
||||
if (val[0] === '') {
|
||||
val.shift()
|
||||
}
|
||||
|
||||
p[field.variable] = val
|
||||
}
|
||||
}
|
||||
|
||||
plugins[plugin.name] = p
|
||||
}
|
||||
|
||||
updateSettings(plugins, 'plugins')
|
||||
.then(() => { this.showSuccess(this.$t('settings.pluginsUpdated')) })
|
||||
.catch(error => { this.showError(error) })
|
||||
},
|
||||
parsePlugin (name, plugin) {
|
||||
let obj = {
|
||||
name: name,
|
||||
fields: []
|
||||
}
|
||||
|
||||
for (let option of plugin) {
|
||||
let value = option.value
|
||||
|
||||
let field = {
|
||||
name: option.name,
|
||||
variable: option.variable,
|
||||
type: 'text',
|
||||
original: 'text',
|
||||
value: value
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
field.original = 'array'
|
||||
field.value = value.join(' ')
|
||||
|
||||
obj.fields.push(field)
|
||||
continue
|
||||
}
|
||||
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
field.type = 'checkbox'
|
||||
field.original = 'boolean'
|
||||
break
|
||||
}
|
||||
|
||||
obj.fields.push(field)
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
53
assets/src/views/Layout.vue
Normal file
53
assets/src/views/Layout.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<site-header></site-header>
|
||||
<sidebar></sidebar>
|
||||
<main>
|
||||
<router-view v-on:css-updated="updateCSS"></router-view>
|
||||
</main>
|
||||
<prompts></prompts>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from '@/components/Search'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
import Prompts from '@/components/prompts/Prompts'
|
||||
import SiteHeader from '@/components/Header'
|
||||
|
||||
export default {
|
||||
name: 'layout',
|
||||
components: {
|
||||
Search,
|
||||
Sidebar,
|
||||
SiteHeader,
|
||||
Prompts
|
||||
},
|
||||
watch: {
|
||||
'$route': function () {
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
if (this.$store.state.show !== 'success') this.$store.commit('closeHovers')
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateCSS()
|
||||
},
|
||||
methods: {
|
||||
updateCSS () {
|
||||
let css = this.$store.state.user.css
|
||||
|
||||
let style = document.querySelector('style[title="user-css"]')
|
||||
if (style !== undefined && style !== null) {
|
||||
style.parentElement.removeChild(style)
|
||||
}
|
||||
|
||||
style = document.createElement('style')
|
||||
style.title = 'user-css'
|
||||
style.type = 'text/css'
|
||||
style.appendChild(document.createTextNode(css))
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
42
assets/src/views/Login.vue
Normal file
42
assets/src/views/Login.vue
Normal file
@@ -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>
|
||||
103
assets/src/views/ProfileSettings.vue
Normal file
103
assets/src/views/ProfileSettings.vue
Normal file
@@ -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>
|
||||
284
assets/src/views/User.vue
Normal file
284
assets/src/views/User.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div>
|
||||
<form @submit="save" class="dashboard">
|
||||
<h1 v-if="id === 0">{{ $t('settings.newUser') }}</h1>
|
||||
<h1 v-else>{{ $t('settings.user') }} {{ username }}</h1>
|
||||
|
||||
<p><label for="username">{{ $t('settings.username') }}</label><input type="text" v-model="username" id="username"></p>
|
||||
<p><label for="password">{{ $t('settings.password') }}</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
|
||||
<p><label for="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
|
||||
<p>
|
||||
<label for="locale">{{ $t('settings.language') }}</label>
|
||||
<languages id="locale" :selected.sync="locale"></languages>
|
||||
</p>
|
||||
|
||||
<h2>{{ $t('settings.permissions') }}</h2>
|
||||
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
|
||||
|
||||
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> {{ $t('settings.allowEdit') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> {{ $t('settings.allowCommands') }}</p>
|
||||
<p v-for="(value, key) in permissions" :key="key">
|
||||
<input type="checkbox" :disabled="admin" v-model="permissions[key]"> {{ capitalize(key) }}
|
||||
</p>
|
||||
|
||||
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||
<input type="text" v-model.trim="commands">
|
||||
|
||||
<h2>{{ $t('settings.rules') }}</h2>
|
||||
|
||||
<p class="small">{{ $t('settings.rulesHelp1') }}</p>
|
||||
|
||||
<i18n path="settings.rulesHelp2" tag="p" class="small">
|
||||
<code>allow</code><code>disallow</code><code>regex</code>
|
||||
</i18n>
|
||||
|
||||
<p class="small"><strong>{{ $t('settings.examples') }}</strong></p>
|
||||
|
||||
<ul class="small">
|
||||
<li><code>disallow regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</li>
|
||||
<li><code>disallow /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
|
||||
</ul>
|
||||
|
||||
<textarea v-model.trim="rules"></textarea>
|
||||
|
||||
<h2>{{ $t('settings.customStylesheet') }}</h2>
|
||||
|
||||
<textarea name="css"></textarea>
|
||||
|
||||
<p>
|
||||
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="delete" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
<input type="submit" :value="$t('buttons.save')">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<div v-if="$store.state.show === 'deleteUser'" class="prompt">
|
||||
<h3>Delete User</h3>
|
||||
<p>Are you sure you want to delete this user?</p>
|
||||
<div>
|
||||
<button @click="deleteUser" autofocus>{{ $t('buttons.delete') }}</button>
|
||||
<button class="cancel"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">
|
||||
{{ $t('buttons.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import { getUser, newUser, updateUser, deleteUser } from '@/utils/api'
|
||||
import Languages from '@/components/Languages'
|
||||
|
||||
export default {
|
||||
name: 'user',
|
||||
components: { Languages },
|
||||
data: () => {
|
||||
return {
|
||||
id: 0,
|
||||
admin: false,
|
||||
allowNew: false,
|
||||
allowEdit: false,
|
||||
allowCommands: false,
|
||||
permissions: {},
|
||||
password: '',
|
||||
username: '',
|
||||
filesystem: '',
|
||||
rules: '',
|
||||
locale: '',
|
||||
css: '',
|
||||
commands: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
passwordPlaceholder () {
|
||||
if (this.$route.path === '/users/new') return ''
|
||||
return this.$t('settings.avoidChanges')
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
admin: function () {
|
||||
if (!this.admin) return
|
||||
this.allowCommands = true
|
||||
this.allowEdit = true
|
||||
this.allowNew = true
|
||||
for (let key in this.permissions) {
|
||||
this.permissions[key] = true
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['closeHovers']),
|
||||
fetchData () {
|
||||
let user = this.$route.params[0]
|
||||
|
||||
if (this.$route.path === '/users/new') {
|
||||
user = 'base'
|
||||
}
|
||||
|
||||
getUser(user).then(user => {
|
||||
this.id = user.ID
|
||||
this.admin = user.admin
|
||||
this.allowCommands = user.allowCommands
|
||||
this.allowNew = user.allowNew
|
||||
this.allowEdit = user.allowEdit
|
||||
this.filesystem = user.filesystem
|
||||
this.username = user.username
|
||||
this.commands = user.commands.join(' ')
|
||||
this.css = user.css
|
||||
this.permissions = user.permissions
|
||||
this.locale = user.locale
|
||||
|
||||
for (let rule of user.rules) {
|
||||
if (rule.allow) {
|
||||
this.rules += 'allow '
|
||||
} else {
|
||||
this.rules += 'disallow '
|
||||
}
|
||||
|
||||
if (rule.regex) {
|
||||
this.rules += 'regex ' + rule.regexp.raw
|
||||
} else {
|
||||
this.rules += rule.path
|
||||
}
|
||||
|
||||
this.rules += '\n'
|
||||
}
|
||||
|
||||
this.rules = this.rules.trim()
|
||||
}).catch(() => {
|
||||
this.$router.push({ path: '/users/new' })
|
||||
})
|
||||
},
|
||||
capitalize (name) {
|
||||
let splitted = name.split(/(?=[A-Z])/)
|
||||
name = ''
|
||||
|
||||
for (let i = 0; i < splitted.length; i++) {
|
||||
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + ' '
|
||||
}
|
||||
|
||||
return name.slice(0, -1)
|
||||
},
|
||||
reset () {
|
||||
this.id = 0
|
||||
this.admin = false
|
||||
this.allowNew = false
|
||||
this.allowEdit = false
|
||||
this.permissins = {}
|
||||
this.allowCommands = false
|
||||
this.password = ''
|
||||
this.username = ''
|
||||
this.filesystem = ''
|
||||
this.rules = ''
|
||||
this.locale = ''
|
||||
this.css = ''
|
||||
this.commands = ''
|
||||
},
|
||||
deletePrompt (event) {
|
||||
this.$store.commit('showHover', 'deleteUser')
|
||||
},
|
||||
deleteUser (event) {
|
||||
event.preventDefault()
|
||||
|
||||
deleteUser(this.id).then(location => {
|
||||
this.$router.push({ path: '/users' })
|
||||
this.$store.commit('showSuccess', this.$t('settings.userDeleted'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
},
|
||||
save (event) {
|
||||
event.preventDefault()
|
||||
let user = this.parseForm()
|
||||
|
||||
if (this.$route.path === '/users/new') {
|
||||
newUser(user).then(location => {
|
||||
this.$router.push({ path: location })
|
||||
this.$store.commit('showSuccess', this.$t('settings.userCreated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
updateUser(user).then(location => {
|
||||
if (user.ID === this.$store.state.user.ID) {
|
||||
this.$store.commit('setUser', user)
|
||||
}
|
||||
|
||||
this.$store.commit('showSuccess', this.$t('settings.userUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
})
|
||||
},
|
||||
parseForm () {
|
||||
let user = {
|
||||
ID: this.id,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
filesystem: this.filesystem,
|
||||
admin: this.admin,
|
||||
allowCommands: this.allowCommands,
|
||||
allowNew: this.allowNew,
|
||||
allowEdit: this.allowEdit,
|
||||
permissions: this.permissions,
|
||||
css: this.css,
|
||||
locale: this.locale,
|
||||
commands: this.commands.split(' '),
|
||||
rules: []
|
||||
}
|
||||
|
||||
let rules = this.rules.split('\n')
|
||||
|
||||
for (let rawRule of rules) {
|
||||
let rule = {
|
||||
allow: true,
|
||||
path: '',
|
||||
regex: false,
|
||||
regexp: {
|
||||
raw: ''
|
||||
}
|
||||
}
|
||||
|
||||
rawRule = rawRule.split(' ')
|
||||
|
||||
// Skip a malformed rule
|
||||
if (rawRule.length < 2) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip a malformed rule
|
||||
if (rawRule[0] !== 'allow' && rawRule[0] !== 'disallow') {
|
||||
continue
|
||||
}
|
||||
|
||||
rule.allow = (rawRule[0] === 'allow')
|
||||
rawRule.shift()
|
||||
|
||||
if (rawRule[0] === 'regex') {
|
||||
rule.regex = true
|
||||
rawRule.shift()
|
||||
rule.regexp.raw = rawRule.join(' ')
|
||||
} else {
|
||||
rule.path = rawRule.join(' ')
|
||||
}
|
||||
|
||||
user.rules.push(rule)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
42
assets/src/views/Users.vue
Normal file
42
assets/src/views/Users.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<h1>{{ $t('settings.users') }} <router-link to="/users/new"><button>{{ $t('buttons.new') }}</button></router-link></h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>{{ $t('settings.username') }}</th>
|
||||
<th>{{ $t('settings.admin') }}</th>
|
||||
<th>{{ $t('settings.scope') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="user in users">
|
||||
<td>{{ user.username }}</td>
|
||||
<td><i v-if="user.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td>
|
||||
<td>{{ user.filesystem }}</td>
|
||||
<td><router-link :to="'/users/' + user.ID"><i class="material-icons">mode_edit</i></router-link></td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'users',
|
||||
data: function () {
|
||||
return {
|
||||
users: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
api.getUsers().then(users => {
|
||||
this.users = users
|
||||
}).catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
13
assets/src/views/errors/403.vue
Normal file
13
assets/src/views/errors/403.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">error</i>
|
||||
<span>{{ $t('errors.forbidden') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'forbidden'}
|
||||
</script>
|
||||
|
||||
13
assets/src/views/errors/404.vue
Normal file
13
assets/src/views/errors/404.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">gps_off</i>
|
||||
<span>{{ $t('errors.notFound') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'not-found'}
|
||||
</script>
|
||||
|
||||
13
assets/src/views/errors/500.vue
Normal file
13
assets/src/views/errors/500.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="message">
|
||||
<i class="material-icons">error_outline</i>
|
||||
<span>{{ $t('errors.internal') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'internal-error'}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user