initial commit

This commit is contained in:
Milos Stojanovic
2019-06-13 18:52:40 +02:00
commit 261607e1d3
160 changed files with 41704 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
<template>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item logo" @click="$router.push('/')">
<img :src="this.$store.state.config.logo">
</a>
<a @click="navbarActive = !navbarActive" role="button" :class="[navbarActive ? 'is-active' : '', 'navbar-burger burger']" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div :class="[navbarActive ? 'is-active' : '', 'navbar-menu']">
<div class="navbar-end">
<a @click="$router.push('/')" v-if="is('admin')" class="navbar-item">
{{ lang('Files') }}
</a>
<a @click="$router.push('/users')" v-if="is('admin')" class="navbar-item">
{{ lang('Users') }}
</a>
<a @click="login" v-if="is('guest')" class="navbar-item">
{{ lang('Login') }}
</a>
<a @click="profile" v-if="!is('guest')" class="navbar-item">
{{ lang('Profile') }}
</a>
<a @click="logout" v-if="!is('guest')" class="navbar-item">
{{ lang('Logout') }}
</a>
</div>
</div>
</nav>
</template>
<script>
import Profile from './Profile'
import api from '../../api/api'
export default {
name: 'Menu',
components: { Profile },
data() {
return {
navbarActive: false,
}
},
mounted() {
if (this.$store.state.user.firstlogin) {
this.profile()
}
},
methods: {
logout() {
api.logout()
.then(() => {
this.$store.commit('initialize')
api.getUser()
.then(user => {
this.$store.commit('setUser', user)
this.$router.push('/login')
})
.catch(() => {
this.$store.commit('initialize')
})
})
.catch(error => {
this.$store.commit('initialize')
this.handleError(error)
})
},
login() {
this.$router.push('/login')
},
profile() {
this.$modal.open({
parent: this,
hasModalCard: true,
component: Profile,
})
},
}
}
</script>
<style scoped>
.navbar {
z-index: 10;
}
@media all and (max-width: 1088px) {
.logo {
padding: 0;
}
.logo img {
max-height: 3rem;
}
}
@media all and (min-width: 1088px) {
.navbar {
padding: 1rem 0;
}
.logo {
padding: 0 0 0 12px;
}
.logo img {
max-height: 2.5rem;
}
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div>
<b-select :value="perpage" @input="$emit('selected', $event)" size="is-small">
<option value="">{{ lang('No pagination') }}</option>
<option value="3">{{ lang('Per page', 3) }}</option>
<option value="5">{{ lang('Per page', 5) }}</option>
<option value="10">{{ lang('Per page', 10) }}</option>
<option value="15">{{ lang('Per page', 15) }}</option>
</b-select>
</div>
</template>
<script>
export default {
name: 'Pagination',
props: [ 'perpage' ]
}
</script>

View File

@@ -0,0 +1,63 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ $store.state.user.name }}</p>
</header>
<section class="modal-card-body">
<form @submit="save">
<b-field :label="lang('Old password')" :type="formErrors.oldpassword ? 'is-danger' : ''" :message="formErrors.oldpassword">
<b-input v-model="oldpassword" @keydown.native="formErrors.oldpassword = ''" required></b-input>
</b-field>
<b-field :label="lang('New password')" :type="formErrors.newpassword ? 'is-danger' : ''" :message="formErrors.newpassword">
<b-input v-model="newpassword" @keydown.native="formErrors.newpassword = ''" password-reveal required></b-input>
</b-field>
</form>
</section>
<footer class="modal-card-foot">
<button class="button" type="button" @click="$parent.close()">{{ lang('Close') }}</button>
<button class="button is-primary" type="button" @click="save">{{ lang('Save') }}</button>
</footer>
</div>
</template>
<script>
import api from '../../api/api'
export default {
name: 'Profile',
data() {
return {
oldpassword: '',
newpassword: '',
formErrors: {},
}
},
methods: {
save() {
api.changePassword({
oldpassword: this.oldpassword,
newpassword: this.newpassword,
})
.then(res => {
this.$toast.open({
message: this.lang('Updated'),
type: 'is-success',
})
this.$parent.close()
})
.catch(errors => {
if (typeof errors.response.data.data != 'object') {
this.handleError(errors)
}
_.forEach(errors.response.data, err => {
_.forEach(err, (val, key) => {
this.formErrors[key] = this.lang(val)
this.$forceUpdate()
})
})
})
},
},
}
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ lang('Select Folder') }}</p>
</header>
<section class="modal-card-body">
<div class="tree">
<ul class="tree-list">
<TreeNode @selected="$emit('selected', $event) && $parent.close()" :node="$store.state.tree"></TreeNode>
</ul>
</div>
</section>
<footer class="modal-card-foot">
<button class="button" type="button" @click="$parent.close()">{{ lang('Close') }}</button>
</footer>
</div>
</template>
<script>
import TreeNode from './TreeNode'
export default {
name: 'Tree',
components: { TreeNode },
}
</script>
<style>
.tree {
min-height: 450px
}
.tree-list ul li {
padding-left: 20px;
margin: 6px 0;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<li class="node-tree">
<b-button :type="button_type" size="is-small" @click="toggleButton(node)">
<span class="icon"><i :class="icon"></i></span>
</b-button>
&nbsp; <a @click="$emit('selected', node)">{{ node.name }}</a>
<ul v-if="node.children && node.children.length">
<TreeNode v-for="child in node.children" :node="child" @selected="$emit('selected', $event)"></TreeNode>
</ul>
</li>
</template>
<script>
import api from '../../api/api'
export default {
name: "TreeNode",
props: {
node: Object
},
data() {
return {
active: false,
button_type: 'is-primary'
}
},
mounted() {
if (this.node.path == '/') {
this.$store.commit('resetTree')
this.toggleButton(this.node)
}
},
computed: {
icon() {
return {
'fas': true,
'mdi-24px': true,
'fa-plus': ! this.active,
'fa-minus': this.active,
}
},
},
methods: {
toggleButton(node) {
if (! this.active) {
this.active = true
this.button_type = 'is-primary is-loading'
api.getDir({
dir: node.path
})
.then(ret => {
this.$store.commit('updateTreeNode', {
children: _.filter(ret.files, ['type', 'dir']),
path: node.path,
})
this.$forceUpdate()
this.button_type = 'is-primary'
})
.catch(error => this.handleError(error))
} else {
this.active = false
this.$store.commit('updateTreeNode', {
children: [],
path: node.path,
})
}
}
}
};
</script>
<style scoped>
a {
color: #373737;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,192 @@
<template>
<div>
<div v-if="visible && dropZone == false" class="progress-box">
<div class="box">
<div>
<div class="is-flex is-justify-between">
<div class="is-flex">
<a @click="toggleWindow">
<b-icon :icon="progressVisible ? 'angle-down' : 'angle-up'"></b-icon>
</a>
<span v-if="activeUploads">
{{ lang('Uploading files', Math.round(resumable.progress()*100), formatBytes(resumable.getSize())) }}
</span>
<span v-if="activeUploads && paused">
({{ lang('Paused') }})
</span>
<span v-if="! activeUploads">
{{ lang('Done') }}
</span>
</div>
<div class="is-flex">
<a v-if="activeUploads" @click="togglePause()">
<b-icon :icon="paused ? 'play-circle' : 'pause-circle'"></b-icon>
</a>
<a @click="closeWindow()" class="progress-icon">
<b-icon icon="times"></b-icon>
</a>
</div>
</div>
<hr>
</div>
<div v-if="progressVisible" class="progress-items">
<div v-for="file in resumable.files.slice().reverse()">
<div>
<div>{{ file.relativePath != '/' ? file.relativePath : '' }}/{{ file.fileName }}</div>
<div class="is-flex is-justify-between">
<progress :class="[file.file.uploadingError ? 'is-danger' : 'is-primary', 'progress is-large']" :value="file.progress()*100" max="100"></progress>
<a v-if="! file.isUploading() && file.file.uploadingError" @click="file.retry()" class="progress-icon">
<b-icon icon="redo" type="is-danger"></b-icon>
</a>
<a v-else @click="file.cancel()" class="progress-icon">
<b-icon :icon="file.isComplete() ? 'check' : 'times'"></b-icon>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Resumable from 'resumablejs'
import Vue from 'vue'
import api from '../../api/api'
import axios from 'axios'
export default {
name: 'Upload',
props: [ 'files', 'dropZone' ],
data() {
return {
resumable: {},
visible: false,
paused: false,
progressVisible: false,
progress: 0,
}
},
watch: {
'files' (files) {
this.visible = true
this.progressVisible = true
_.forEach(files, file => {
file.relativePath = this.$store.state.cwd.location
this.resumable.addFile(file)
})
},
},
mounted() {
this.resumable = new Resumable({
target: Vue.config.baseURL+'/upload',
headers: {
'x-csrf-token': axios.defaults.headers.common['x-csrf-token']
},
withCredentials: true,
simultaneousUploads: this.$store.state.config.upload_simultaneous,
chunkSize: this.$store.state.config.upload_chunk_size,
maxFileSize: this.$store.state.config.upload_max_size,
maxFileSizeErrorCallback: (file, errorCount) => {
this.$notification.open({
message: this.lang('File size error', file.name, this.formatBytes(this.$store.state.config.upload_max_size)),
type: 'is-danger',
queue: false,
indefinite: true,
})
}
})
if (! this.resumable.support) {
this.$dialog.alert({
type: 'is-danger',
message: this.lang('Browser not supported.'),
})
return;
}
this.resumable.on('fileAdded', (file) => {
if (! this.paused) {
this.resumable.upload()
}
})
this.resumable.on('fileSuccess', (file) => {
file.file.uploadingError = false
this.$forceUpdate()
if (this.can('read')) {
api.getDir({
to: '',
})
.then(ret => {
this.$store.commit('setCwd', {
content: ret.files,
location: ret.location,
})
})
.catch(error => this.handleError(error))
}
})
this.resumable.on('fileError', (file) => {
file.file.uploadingError = true
})
},
computed: {
activeUploads() {
return this.resumable.files.length && this.resumable.progress() < 1
},
},
methods: {
closeWindow() {
if (this.activeUploads) {
this.$dialog.confirm({
message: this.lang('Are you sure you want to stop all uploads?'),
type: 'is-danger',
cancelText: this.lang('Cancel'),
confirmText: this.lang('Confirm'),
onConfirm: () => {
this.resumable.cancel()
this.visible = false
}
})
} else {
this.visible = false
this.resumable.cancel()
}
},
toggleWindow() {
this.progressVisible = ! this.progressVisible
},
togglePause() {
if (this.paused) {
this.resumable.upload()
this.paused = false
} else {
this.resumable.pause()
this.paused = true
}
},
},
}
</script>
<style scoped>
.progress-icon {
margin-left: 15px;
}
.progress-box {
position: fixed;
width: 100%;
bottom: -30px;
left: 0;
padding: 25px;
max-height: 50%;
z-index: 1;
}
.progress-items {
overflow-y: scroll;
margin-right: -100px;
padding-right: 100px;
max-height: 300px; /* fix this */
}
</style>

View File

@@ -0,0 +1,206 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ user.name }}</p>
</header>
<section class="modal-card-body">
<form @submit.prevent="save">
<div v-if="user.role == 'user' || user.role == 'admin'" class="field">
<b-field :label="lang('Role')">
<b-select v-model="formFields.role" :placeholder="lang('Role')" expanded required>
<option value="user" key="user">{{ lang('User') }}</option>
<option value="admin" key="admin">{{ lang('Admin') }}</option>
</b-select>
</b-field>
<b-field :label="lang('Username')" :type="formErrors.username ? 'is-danger' : ''" :message="formErrors.username">
<b-input v-model="formFields.username" @keydown.native="formErrors.username = ''"></b-input>
</b-field>
<b-field :label="lang('Name')" :type="formErrors.name ? 'is-danger' : ''" :message="formErrors.name">
<b-input v-model="formFields.name" @keydown.native="formErrors.name = ''"></b-input>
</b-field>
<b-field :label="lang('Password')" :type="formErrors.password ? 'is-danger' : ''" :message="formErrors.password">
<b-input v-model="formFields.password" @keydown.native="formErrors.password = ''" :placeholder="action == 'edit' ? lang('Leave blank for no change') : ''" password-reveal></b-input>
</b-field>
</div>
<b-field :label="lang('Homedir')" :type="formErrors.homedir ? 'is-danger' : ''" :message="formErrors.homedir">
<b-input v-model="formFields.homedir" @focus="selectDir"></b-input>
</b-field>
<b-field :label="lang('Permissions')">
<div class="block">
<b-checkbox v-model="permissions.read">
{{ lang('Read') }}
</b-checkbox>
<b-checkbox v-model="permissions.write">
{{ lang('Write') }}
</b-checkbox>
<b-checkbox v-model="permissions.upload">
{{ lang('Upload') }}
</b-checkbox>
<b-checkbox v-model="permissions.download">
{{ lang('Download') }}
</b-checkbox>
<b-checkbox v-model="permissions.batchdownload">
{{ lang('Batch Download') }}
</b-checkbox>
<b-checkbox v-model="permissions.zip">
{{ lang('Zip') }}
</b-checkbox>
</div>
</b-field>
</form>
</section>
<footer class="modal-card-foot">
<button class="button" type="button" @click="$parent.close()">{{ lang('Close') }}</button>
<button class="button is-primary" type="button" @click="confirmSave">{{ lang('Save') }}</button>
</footer>
</div>
</template>
<script>
import Tree from './Tree'
import api from '../../api/api'
export default {
name: 'UserEdit',
components: { Tree },
props: [ 'user', 'action' ],
computed: {
},
data() {
return {
formFields: {
role: this.user.role,
name: this.user.name,
username: this.user.username,
homedir: this.user.homedir,
password: '',
},
formErrors: {},
permissions: {
read: _.find(this.user.permissions, p => p == 'read') ? true : false,
write: _.find(this.user.permissions, p => p == 'write') ? true : false,
upload: _.find(this.user.permissions, p => p == 'upload') ? true : false,
download: _.find(this.user.permissions, p => p == 'download') ? true : false,
batchdownload: _.find(this.user.permissions, p => p == 'batchdownload') ? true : false,
zip: _.find(this.user.permissions, p => p == 'zip') ? true : false,
}
}
},
watch: {
'permissions.read' (val) {
if (!val) {
this.permissions.write = false
this.permissions.batchdownload = false
this.permissions.zip = false
}
},
'permissions.write' (val) {
if (val) {
this.permissions.read = true
} else {
this.permissions.zip = false
}
},
'permissions.download' (val) {
if (!val) {
this.permissions.batchdownload = false
}
},
'permissions.batchdownload' (val) {
if (val) {
this.permissions.read = true
this.permissions.download = true
}
},
'permissions.zip' (val) {
if (val) {
this.permissions.read = true
this.permissions.write = true
}
},
},
methods: {
selectDir() {
this.formErrors.homedir = ''
this.$modal.open({
parent: this,
hasModalCard: true,
component: Tree,
events: {
selected: dir => {
this.formFields.homedir = dir.path
}
},
})
},
getPermissionsArray() {
return _.reduce(this.permissions, (result, value, key) => {
if (value == true) {
result.push(key)
}
return result
}, [])
},
confirmSave() {
if (this.formFields.role == 'guest' && this.getPermissionsArray().length) {
this.$dialog.confirm({
message: this.lang('Are you sure you want to allow access to everyone?'),
type: 'is-danger',
cancelText: this.lang('Cancel'),
confirmText: this.lang('Confirm'),
onConfirm: () => {
this.save()
}
})
} else {
this.save()
}
},
save() {
let method = this.action == 'add' ? api.storeUser : api.updateUser
method({
key: this.user.username,
role: this.formFields.role,
name: this.formFields.name,
username: this.formFields.username,
homedir: this.formFields.homedir,
password: this.formFields.password,
permissions: this.getPermissionsArray(),
})
.then(res => {
this.$toast.open({
message: this.lang('Updated'),
type: 'is-success',
})
this.$emit('updated', res)
this.$parent.close()
})
.catch(errors => {
if (typeof errors.response.data.data != 'object') {
this.handleError(errors)
}
_.forEach(errors.response.data, err => {
_.forEach(err, (val, key) => {
this.formErrors[key] = this.lang(val)
this.$forceUpdate()
})
})
})
},
},
}
</script>