Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f7e6cbb52 | ||
|
|
40fe081962 | ||
|
|
ebf1325126 | ||
|
|
ea1b9febb7 | ||
|
|
f3edf63fb2 | ||
|
|
f13e7ba940 | ||
|
|
3ebe219e96 | ||
|
|
d838856711 | ||
|
|
610d55c26f | ||
|
|
ac044016ee | ||
|
|
58af3461a8 | ||
|
|
4191a6f9e0 |
@@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="base" content="{{ .BaseURL }}">
|
||||
<meta name="staticgen" content="{{ .StaticGen }}">
|
||||
<meta name="noauth" content="{{ .NoAuth }}">
|
||||
<title>File Manager</title>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.user.noAuth">
|
||||
<div v-if="!$store.state.noAuth">
|
||||
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')">
|
||||
<i class="material-icons">settings_applications</i>
|
||||
<span>{{ $t('sidebar.settings') }}</span>
|
||||
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
buttons.done(button)
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
this.$store.commit('setSchedule', '')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,17 +210,13 @@ export default {
|
||||
if (this.$store.state.clipboard.key === 'x') {
|
||||
api.move(items).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
})
|
||||
}).catch(this.$showError)
|
||||
return
|
||||
}
|
||||
|
||||
api.copy(items).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
})
|
||||
}).catch(this.$showError)
|
||||
},
|
||||
resizeEvent () {
|
||||
// Update the columns size based on the window width.
|
||||
@@ -267,7 +263,7 @@ export default {
|
||||
.then(req => {
|
||||
this.checkConflict(files, req.items, base)
|
||||
})
|
||||
.catch(error => { console.log(error) })
|
||||
.catch(this.$showError)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -348,7 +344,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
finish()
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
|
||||
return false
|
||||
|
||||
@@ -109,21 +109,24 @@ export default {
|
||||
.then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
})
|
||||
.catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
})
|
||||
.catch(this.$showError)
|
||||
},
|
||||
click: function (event) {
|
||||
if (this.selectedCount !== 0) event.preventDefault()
|
||||
if (this.$store.state.selected.indexOf(this.index) === -1) {
|
||||
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
|
||||
|
||||
this.addSelected(this.index)
|
||||
} else {
|
||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
||||
this.removeSelected(this.index)
|
||||
return
|
||||
}
|
||||
|
||||
return false
|
||||
if (event.shiftKey && this.selected.length === 1) {
|
||||
let fi = (this.index > this.selected[0]) ? this.selected[0] : this.index
|
||||
let la = (this.index > this.selected[0]) ? this.index : this.selected[0]
|
||||
for (; fi <= la; fi++) this.addSelected(fi)
|
||||
return
|
||||
}
|
||||
|
||||
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
|
||||
this.addSelected(this.index)
|
||||
},
|
||||
touchstart (event) {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -75,7 +75,7 @@ export default {
|
||||
this.listing = req
|
||||
this.updateLinks()
|
||||
})
|
||||
.catch(error => { console.log(error) })
|
||||
.catch(this.$showError)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keyup', this.key)
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
buttons.done('copy')
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
buttons.done('delete')
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
|
||||
return
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
.catch(error => {
|
||||
buttons.done('delete')
|
||||
this.$store.commit('setReload', true)
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<div class="prompt error">
|
||||
<i class="material-icons">error_outline</i>
|
||||
<h3>{{ $t('prompts.error') }}</h3>
|
||||
<pre>{{ $store.state.showMessage }}</pre>
|
||||
<div>
|
||||
<button @click="close"
|
||||
autofocus
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
||||
<button @click="reportIssue"
|
||||
class="cancel"
|
||||
:aria-label="$t('buttons.reportIssue')"
|
||||
:title="$t('buttons.reportIssue')">{{ $t('buttons.reportIssue') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'error',
|
||||
methods: {
|
||||
reportIssue () {
|
||||
window.open('https://github.com/hacdias/filemanager/issues/new')
|
||||
},
|
||||
close () {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
// so we fetch the data from the previous directory.
|
||||
api.fetch(url.removeLastDir(this.$route.path))
|
||||
.then(this.fillOptions)
|
||||
.catch(this.showError)
|
||||
.catch(this.$showError)
|
||||
},
|
||||
methods: {
|
||||
fillOptions (req) {
|
||||
@@ -96,7 +96,7 @@ export default {
|
||||
|
||||
api.fetch(uri)
|
||||
.then(this.fillOptions)
|
||||
.catch(this.showError)
|
||||
.catch(this.$showError)
|
||||
},
|
||||
touchstart (event) {
|
||||
let url = event.currentTarget.dataset.url
|
||||
|
||||
@@ -111,7 +111,7 @@ export default {
|
||||
|
||||
api.checksum(link, hash)
|
||||
.then((hash) => { event.target.innerHTML = hash })
|
||||
.catch(error => { this.$store.commit('showError', error) })
|
||||
.catch(this.$showError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
buttons.done('move')
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
@@ -37,9 +37,7 @@ export default {
|
||||
.then((url) => {
|
||||
this.$router.push({ path: url })
|
||||
})
|
||||
.catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
})
|
||||
.catch(this.$showError)
|
||||
},
|
||||
new (url, type) {
|
||||
url = removePrefix(url)
|
||||
@@ -47,7 +45,7 @@ export default {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${this.$store.state.baseURL}/api/resource${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${this.$store.state.jwt}`)
|
||||
if (!this.$store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${this.$store.state.jwt}`)
|
||||
request.setRequestHeader('Archetype', encodeURIComponent(type))
|
||||
|
||||
request.onload = () => {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
|
||||
api.post(uri)
|
||||
.then(() => { this.$router.push({ path: uri }) })
|
||||
.catch(error => { this.$store.commit('showError', error) })
|
||||
.catch(this.$showError)
|
||||
|
||||
// Close the prompt
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
// Create the new file.
|
||||
api.post(uri)
|
||||
.then(() => { this.$router.push({ path: uri }) })
|
||||
.catch(error => { this.$store.commit('showError', error) })
|
||||
.catch(this.$showError)
|
||||
|
||||
// Close the prompt.
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<info v-else-if="showInfo"></info>
|
||||
<move v-else-if="showMove"></move>
|
||||
<copy v-else-if="showCopy"></copy>
|
||||
<error v-else-if="showError"></error>
|
||||
<success v-else-if="showSuccess"></success>
|
||||
<replace v-else-if="showReplace"></replace>
|
||||
<schedule v-else-if="show === 'schedule'"></schedule>
|
||||
<new-archetype v-else-if="show === 'new-archetype'"></new-archetype>
|
||||
@@ -27,8 +25,6 @@ import Rename from './Rename'
|
||||
import Download from './Download'
|
||||
import Move from './Move'
|
||||
import Copy from './Copy'
|
||||
import Error from './Error'
|
||||
import Success from './Success'
|
||||
import NewFile from './NewFile'
|
||||
import NewDir from './NewDir'
|
||||
import NewArchetype from './NewArchetype'
|
||||
@@ -47,9 +43,7 @@ export default {
|
||||
NewArchetype,
|
||||
Schedule,
|
||||
Rename,
|
||||
Error,
|
||||
Download,
|
||||
Success,
|
||||
Move,
|
||||
Copy,
|
||||
Share,
|
||||
@@ -70,8 +64,6 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(['show', 'plugins']),
|
||||
showError: function () { return this.show === 'error' },
|
||||
showSuccess: function () { return this.show === 'success' },
|
||||
showInfo: function () { return this.show === 'info' },
|
||||
showHelp: function () { return this.show === 'help' },
|
||||
showDelete: function () { return this.show === 'delete' },
|
||||
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
}
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
|
||||
<button class="action copy"
|
||||
<button class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
@@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
import { getShare, deleteShare, share } from '@/utils/api'
|
||||
import moment from 'moment'
|
||||
import Clipboard from 'clipboard'
|
||||
@@ -101,20 +101,25 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
if (error === 404) return
|
||||
this.showError(error)
|
||||
this.$showError(error)
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
this.clip = new Clipboard('.copy')
|
||||
this.clip = new Clipboard('.copy-clipboard')
|
||||
this.clip.on('success', (e) => {
|
||||
this.$showSuccess(this.$t('success.linkCopied'))
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.clip.destroy()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showError' ]),
|
||||
submit: function (event) {
|
||||
if (!this.time) return
|
||||
|
||||
share(this.url, this.time, this.unit)
|
||||
.then(result => { this.links.push(result); this.sort() })
|
||||
.catch(error => { this.showError(error) })
|
||||
.catch(this.$showError)
|
||||
},
|
||||
getPermalink (event) {
|
||||
share(this.url)
|
||||
@@ -123,7 +128,7 @@ export default {
|
||||
this.sort()
|
||||
this.hasPermanent = true
|
||||
})
|
||||
.catch(error => { this.showError(error) })
|
||||
.catch(this.$showError)
|
||||
},
|
||||
deleteLink (event, link) {
|
||||
event.preventDefault()
|
||||
@@ -132,7 +137,7 @@ export default {
|
||||
if (!link.expires) this.hasPermanent = false
|
||||
this.links = this.links.filter(item => item.hash !== link.hash)
|
||||
})
|
||||
.catch(error => { this.showError(error) })
|
||||
.catch(this.$showError)
|
||||
},
|
||||
humanTime (time) {
|
||||
return moment(time).fromNow()
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="prompt success">
|
||||
<i class="material-icons">done</i>
|
||||
<h3>{{ $store.state.showMessage }}</h3>
|
||||
<div>
|
||||
<button @click="close"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')"
|
||||
autofocus>{{ $t('buttons.ok') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'success',
|
||||
methods: {
|
||||
close () {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,6 @@ body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
padding-top: 4em;
|
||||
background-color: #f8f8f8;
|
||||
user-select: none;
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
|
||||
@@ -206,3 +206,24 @@
|
||||
margin-right: .5em;
|
||||
border: 1px solid #dadada;
|
||||
}
|
||||
|
||||
.prompt#share .action.copy-clipboard::after {
|
||||
content: 'Copied!';
|
||||
position: absolute;
|
||||
left: -25%;
|
||||
width: 150%;
|
||||
font-size: .6em;
|
||||
text-align: center;
|
||||
background: #44a6f5;
|
||||
color: #fff;
|
||||
padding: .5em .2em;
|
||||
border-radius: .4em;
|
||||
top: -2em;
|
||||
transition: .1s ease opacity;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.prompt#share .action.copy-clipboard.active::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "~normalize.css/normalize.css";
|
||||
@import "~noty/lib/noty.css";
|
||||
@import "./fonts.css";
|
||||
@import "./base.css";
|
||||
@import "./header.css";
|
||||
@@ -180,6 +181,17 @@
|
||||
* PROMPT *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
.noty_buttons {
|
||||
text-align: right;
|
||||
padding: 0 10px 10px !important;
|
||||
}
|
||||
|
||||
.noty_buttons button {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
box-shadow: 0 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* FOOTER *
|
||||
|
||||
@@ -26,11 +26,13 @@ buttons:
|
||||
publish: Publish
|
||||
selectMultiple: Select multiple
|
||||
schedule: Schedule
|
||||
switchView: Swicth view
|
||||
switchView: Switch view
|
||||
toggleSidebar: Toggle sidebar
|
||||
update: Update
|
||||
upload: Upload
|
||||
permalink: Get Permanent Link
|
||||
success:
|
||||
linkCopied: Link copied!
|
||||
errors:
|
||||
forbidden: You're not welcome here.
|
||||
internal: Something really went wrong.
|
||||
@@ -122,6 +124,7 @@ settings:
|
||||
examples: Examples
|
||||
globalSettings: Global Settings
|
||||
language: Language
|
||||
lockPassword: Prevent the user from changing the password
|
||||
newPassword: Your new password
|
||||
newPasswordConfirm: Confirm your new password
|
||||
newUser: New User
|
||||
|
||||
@@ -31,6 +31,8 @@ buttons:
|
||||
update: 更新
|
||||
upload: アップロード
|
||||
permalink: 固定リンク
|
||||
success:
|
||||
linkCopied: リンクがコピーされました!
|
||||
errors:
|
||||
forbidden: アクセスが拒否されました。
|
||||
internal: 内部エラーが発生しました。
|
||||
@@ -122,6 +124,7 @@ settings:
|
||||
examples: 例
|
||||
globalSettings: グローバル設定
|
||||
language: 言語
|
||||
lockPassowrd: 新しいパスワードを変更に禁止
|
||||
newPassword: 新しいパスワード
|
||||
newPasswordConfirm: 新しいパスワードを確認します
|
||||
newUser: 新しいユーザー
|
||||
|
||||
@@ -31,6 +31,8 @@ buttons:
|
||||
update: Atualizar
|
||||
upload: Enviar
|
||||
permalink: Obter link permanente
|
||||
success:
|
||||
linkCopied: Link copiado!
|
||||
errors:
|
||||
forbidden: Tu não és bem-vindo aqui.
|
||||
internal: Algo correu bastante mal.
|
||||
@@ -143,6 +145,7 @@ settings:
|
||||
examples: Exemplos
|
||||
globalSettings: Configurações Globais
|
||||
language: Linguagem
|
||||
lockPassword: Não permitir que o utilizador altere a palavra-passe
|
||||
newPassword: Nova palavra-passe
|
||||
newPasswordConfirm: Confirme a nova palavra-passe
|
||||
newUser: Novo Utilizador
|
||||
|
||||
@@ -31,6 +31,8 @@ buttons:
|
||||
update: 更新
|
||||
upload: 上传
|
||||
permalink: 获取永久链接
|
||||
success:
|
||||
linkCopied: 链接已复制!
|
||||
errors:
|
||||
forbidden: 你被禁止访问。
|
||||
internal: 内部出现麻烦了。
|
||||
@@ -121,6 +123,7 @@ settings:
|
||||
examples: 例子
|
||||
globalSettings: 全局设置
|
||||
language: 语言
|
||||
lockPassowrd: 禁止用户修改密码
|
||||
newPassword: 您的新密码
|
||||
newPasswordConfirm: 重输一遍新密码
|
||||
newUser: 新建用户
|
||||
|
||||
@@ -31,6 +31,8 @@ buttons:
|
||||
update: 更新
|
||||
upload: 上傳
|
||||
permalink: 獲取永久連結
|
||||
success:
|
||||
linkCopied: 連結已複製!
|
||||
errors:
|
||||
forbidden: 你被禁止訪問。
|
||||
internal: 內部出現麻煩了。
|
||||
@@ -121,6 +123,7 @@ settings:
|
||||
examples: 例子
|
||||
globalSettings: 全域設定
|
||||
language: 語言
|
||||
lockPassword: 禁止用戶修改密碼
|
||||
newPassword: 您的新密碼
|
||||
newPasswordConfirm: 重輸一遍新密碼
|
||||
newUser: 建立用戶
|
||||
|
||||
@@ -3,9 +3,47 @@ import App from './App'
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
import Noty from 'noty'
|
||||
|
||||
Vue.config.productionTip = true
|
||||
|
||||
const notyDefault = {
|
||||
type: 'info',
|
||||
layout: 'bottomRight',
|
||||
timeout: 1000,
|
||||
progressBar: true
|
||||
}
|
||||
|
||||
Vue.prototype.$noty = function (opts) {
|
||||
new Noty(Object.assign({}, notyDefault, opts)).show()
|
||||
}
|
||||
|
||||
Vue.prototype.$showSuccess = function (message) {
|
||||
new Noty(Object.assign({}, notyDefault, {
|
||||
text: message,
|
||||
type: 'success'
|
||||
})).show()
|
||||
}
|
||||
|
||||
Vue.prototype.$showError = function (error) {
|
||||
// TODO: add btns: close and report issue
|
||||
let n = new Noty(Object.assign({}, notyDefault, {
|
||||
text: error,
|
||||
type: 'error',
|
||||
timeout: null,
|
||||
buttons: [
|
||||
Noty.button(i18n.t('buttons.reportIssue'), 'cancel', function () {
|
||||
window.open('https://github.com/hacdias/filemanager/issues/new')
|
||||
}),
|
||||
Noty.button(i18n.t('buttons.close'), '', function () {
|
||||
n.close()
|
||||
})
|
||||
]
|
||||
}))
|
||||
|
||||
n.show()
|
||||
}
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
|
||||
@@ -14,6 +14,7 @@ const state = {
|
||||
},
|
||||
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||
noAuth: (document.querySelector('meta[name="noauth"]').getAttribute('content') === 'true'),
|
||||
jwt: '',
|
||||
progress: 0,
|
||||
schedule: '',
|
||||
|
||||
@@ -18,7 +18,7 @@ export function fetch (url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/resource${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -41,7 +41,7 @@ export function remove (url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('DELETE', `${store.state.baseURL}/api/resource${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
@@ -62,7 +62,7 @@ export function post (url, content = '', overwrite = false, onupload) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${store.state.baseURL}/api/resource${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
if (typeof onupload === 'function') {
|
||||
request.upload.onprogress = onupload
|
||||
@@ -95,7 +95,7 @@ export function put (url, content = '', publish = false, date = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/resource${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
request.setRequestHeader('Publish', publish)
|
||||
|
||||
if (date !== '') {
|
||||
@@ -125,7 +125,7 @@ function moveCopy (items, copy = false) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PATCH', `${store.state.baseURL}/api/resource${from}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
request.setRequestHeader('Destination', to)
|
||||
|
||||
if (copy) {
|
||||
@@ -162,7 +162,7 @@ export function checksum (url, algo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/checksum${url}?algo=${algo}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
@@ -226,7 +226,7 @@ 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}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -255,7 +255,7 @@ export function updateSettings (param, which) {
|
||||
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/settings/`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -278,7 +278,7 @@ export function getUsers () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/users/`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -299,7 +299,7 @@ export function getUser (id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/users/${id}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -320,7 +320,7 @@ export function newUser (user) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${store.state.baseURL}/api/users/`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -345,7 +345,7 @@ export function updateUser (user, which) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('PUT', `${store.state.baseURL}/api/users/${user.ID}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -370,7 +370,7 @@ export function deleteUser (id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('DELETE', `${store.state.baseURL}/api/users/${id}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
switch (request.status) {
|
||||
@@ -395,7 +395,7 @@ export function getShare (url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/share${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
@@ -414,7 +414,7 @@ export function deleteShare (hash) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('DELETE', `${store.state.baseURL}/api/share/${hash}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
@@ -439,7 +439,7 @@ export function share (url, expires = '', unit = 'hours') {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', url, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
|
||||
@@ -16,7 +16,7 @@ function loggedIn () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('GET', `${store.state.baseURL}/api/auth/renew`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${cookie('auth')}`)
|
||||
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${cookie('auth')}`)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
import { getSettings, updateSettings } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
@@ -73,10 +73,9 @@ export default {
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(error => { this.showError(error) })
|
||||
.catch(error => { this.$showError(error) })
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showSuccess', 'showError' ]),
|
||||
capitalize (name, where = '_') {
|
||||
if (where === 'caps') where = /(?=[A-Z])/
|
||||
let splitted = name.split(where)
|
||||
@@ -103,8 +102,8 @@ export default {
|
||||
}
|
||||
|
||||
updateSettings(commands, 'commands')
|
||||
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
|
||||
.catch(error => { this.showError(error) })
|
||||
.then(() => { this.$showSuccess(this.$t('settings.commandsUpdated')) })
|
||||
.catch(error => { this.$showError(error) })
|
||||
},
|
||||
saveStaticGen (event) {
|
||||
event.preventDefault()
|
||||
@@ -124,8 +123,8 @@ export default {
|
||||
}
|
||||
|
||||
updateSettings(staticGen, 'staticGen')
|
||||
.then(() => { this.showSuccess(this.$t('settings.settingsUpdated')) })
|
||||
.catch(error => { this.showError(error) })
|
||||
.then(() => { this.$showSuccess(this.$t('settings.settingsUpdated')) })
|
||||
.catch(error => { this.$showError(error) })
|
||||
},
|
||||
parseStaticGen (staticgen) {
|
||||
for (let option of staticgen) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<p><input type="submit" :value="$t('buttons.update')"></p>
|
||||
</form>
|
||||
|
||||
<form @submit="updatePassword">
|
||||
<form v-if="!user.lockPassword" @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>
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
import { updateUser } from '@/utils/api'
|
||||
import Languages from '@/components/Languages'
|
||||
|
||||
@@ -64,7 +64,6 @@ export default {
|
||||
this.locale = this.user.locale
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'showSuccess' ]),
|
||||
updatePassword (event) {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -78,9 +77,9 @@ export default {
|
||||
}
|
||||
|
||||
updateUser(user, 'password').then(location => {
|
||||
this.showSuccess(this.$t('settings.passwordUpdated'))
|
||||
this.$showSuccess(this.$t('settings.passwordUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
this.$showError(e)
|
||||
})
|
||||
},
|
||||
updateSettings (event) {
|
||||
@@ -93,9 +92,9 @@ export default {
|
||||
updateUser(user, 'partial').then(location => {
|
||||
this.$store.commit('setUser', user)
|
||||
this.$emit('css-updated')
|
||||
this.showSuccess(this.$t('settings.settingsUpdated'))
|
||||
this.$showSuccess(this.$t('settings.settingsUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
this.$showError(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<languages id="locale" :selected.sync="locale"></languages>
|
||||
</p>
|
||||
|
||||
<p><input type="checkbox" :disabled="admin" v-model="lockPassword"> {{ $t('settings.lockPassword') }}</p>
|
||||
|
||||
<h2>{{ $t('settings.permissions') }}</h2>
|
||||
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
|
||||
|
||||
@@ -93,6 +95,7 @@ export default {
|
||||
allowEdit: false,
|
||||
allowCommands: false,
|
||||
allowPublish: false,
|
||||
lockPassword: false,
|
||||
permissions: {},
|
||||
password: '',
|
||||
username: '',
|
||||
@@ -120,6 +123,7 @@ export default {
|
||||
this.allowEdit = true
|
||||
this.allowNew = true
|
||||
this.allowPublish = true
|
||||
this.lockPassword = false
|
||||
for (let key in this.permissions) {
|
||||
this.permissions[key] = true
|
||||
}
|
||||
@@ -141,6 +145,7 @@ export default {
|
||||
this.allowNew = user.allowNew
|
||||
this.allowEdit = user.allowEdit
|
||||
this.allowPublish = user.allowPublish
|
||||
this.lockPassword = user.lockPassword
|
||||
this.filesystem = user.filesystem
|
||||
this.username = user.username
|
||||
this.commands = user.commands.join(' ')
|
||||
@@ -187,6 +192,7 @@ export default {
|
||||
this.allowPublish = false
|
||||
this.permissins = {}
|
||||
this.allowCommands = false
|
||||
this.lockPassword = false
|
||||
this.password = ''
|
||||
this.username = ''
|
||||
this.filesystem = ''
|
||||
@@ -203,9 +209,9 @@ export default {
|
||||
|
||||
deleteUser(this.id).then(location => {
|
||||
this.$router.push({ path: '/users' })
|
||||
this.$store.commit('showSuccess', this.$t('settings.userDeleted'))
|
||||
this.$showSuccess(this.$t('settings.userDeleted'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
this.$showError(e)
|
||||
})
|
||||
},
|
||||
save (event) {
|
||||
@@ -215,9 +221,9 @@ export default {
|
||||
if (this.$route.path === '/users/new') {
|
||||
newUser(user).then(location => {
|
||||
this.$router.push({ path: location })
|
||||
this.$store.commit('showSuccess', this.$t('settings.userCreated'))
|
||||
this.$showSuccess(this.$t('settings.userCreated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
this.$showError(e)
|
||||
})
|
||||
|
||||
return
|
||||
@@ -228,9 +234,9 @@ export default {
|
||||
this.$store.commit('setUser', user)
|
||||
}
|
||||
|
||||
this.$store.commit('showSuccess', this.$t('settings.userUpdated'))
|
||||
this.$showSuccess(this.$t('settings.userUpdated'))
|
||||
}).catch(e => {
|
||||
this.$store.commit('showError', e)
|
||||
this.$showError(e)
|
||||
})
|
||||
},
|
||||
parseForm () {
|
||||
@@ -238,6 +244,7 @@ export default {
|
||||
ID: this.id,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
lockPassword: this.lockPassword,
|
||||
filesystem: this.filesystem,
|
||||
admin: this.admin,
|
||||
allowCommands: this.allowCommands,
|
||||
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
api.getUsers().then(users => {
|
||||
this.users = users
|
||||
}).catch(error => {
|
||||
this.$store.commit('showError', error)
|
||||
this.$showError(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ var (
|
||||
logfile string
|
||||
staticg string
|
||||
locale string
|
||||
baseurl string
|
||||
prefixurl string
|
||||
port int
|
||||
noAuth bool
|
||||
allowCommands bool
|
||||
@@ -49,7 +51,9 @@ func init() {
|
||||
flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
|
||||
flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
|
||||
flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
|
||||
flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
|
||||
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
||||
flag.StringVarP(&prefixurl, "prefixurl", "p", "", "Prefix URL")
|
||||
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(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
|
||||
@@ -74,6 +78,8 @@ func setupViper() {
|
||||
viper.SetDefault("StaticGen", "")
|
||||
viper.SetDefault("Locale", "en")
|
||||
viper.SetDefault("NoAuth", false)
|
||||
viper.SetDefault("BaseURL", "")
|
||||
viper.SetDefault("PrefixURL", "")
|
||||
|
||||
viper.BindPFlag("Port", flag.Lookup("port"))
|
||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||
@@ -88,6 +94,8 @@ func setupViper() {
|
||||
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
||||
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
|
||||
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
||||
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
||||
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
||||
|
||||
viper.SetConfigName("filemanager")
|
||||
viper.AddConfigPath(".")
|
||||
@@ -177,8 +185,8 @@ func handler() http.Handler {
|
||||
|
||||
fm := &filemanager.FileManager{
|
||||
NoAuth: viper.GetBool("NoAuth"),
|
||||
BaseURL: "",
|
||||
PrefixURL: "",
|
||||
BaseURL: viper.GetString("BaseURL"),
|
||||
PrefixURL: viper.GetString("PrefixURL"),
|
||||
DefaultUser: &filemanager.User{
|
||||
AllowCommands: viper.GetBool("AllowCommands"),
|
||||
AllowEdit: viper.GetBool("AllowEdit"),
|
||||
|
||||
@@ -286,6 +286,7 @@ var DefaultUser = User{
|
||||
AllowEdit: true,
|
||||
AllowNew: true,
|
||||
AllowPublish: true,
|
||||
LockPassword: false,
|
||||
Commands: []string{},
|
||||
Rules: []*Rule{},
|
||||
CSS: "",
|
||||
@@ -325,6 +326,9 @@ type User struct {
|
||||
// Locale is the language of the user.
|
||||
Locale string `json:"locale"`
|
||||
|
||||
// Prevents the user to change its password.
|
||||
LockPassword bool `json:"lockPassword"`
|
||||
|
||||
// These indicate if the user can perform certain actions.
|
||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
||||
|
||||
@@ -59,7 +59,6 @@ func renewAuthHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
|
||||
// claims is the JWT claims.
|
||||
type claims struct {
|
||||
fm.User
|
||||
NoAuth bool `json:"noAuth"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
@@ -74,7 +73,6 @@ func printToken(c *fm.Context, w http.ResponseWriter) (int, error) {
|
||||
// Builds the claims.
|
||||
claims := claims{
|
||||
u,
|
||||
c.NoAuth,
|
||||
jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
|
||||
Issuer: "File Manager",
|
||||
|
||||
@@ -223,7 +223,10 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType s
|
||||
tpl := template.Must(template.New("file").Parse(file))
|
||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||
|
||||
data := map[string]interface{}{"BaseURL": c.RootURL()}
|
||||
data := map[string]interface{}{
|
||||
"BaseURL": c.RootURL(),
|
||||
"NoAuth": c.NoAuth,
|
||||
}
|
||||
|
||||
if c.StaticGen != nil {
|
||||
data["StaticGen"] = c.StaticGen.Name()
|
||||
|
||||
@@ -160,7 +160,7 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
// Discard any invalid upload before returning to avoid connection
|
||||
// reset fm.Error.
|
||||
// reset error.
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, r.Body)
|
||||
}()
|
||||
@@ -179,9 +179,9 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
// If using POST method, we are trying to create a new file so it is not
|
||||
// desirable to ovfm.Erride an already existent file. Thus, we check
|
||||
// desirable to override an already existent file. Thus, we check
|
||||
// if the file already exists. If so, we just return a 409 Conflict.
|
||||
if r.Method == http.MethodPost && r.Header.Get("Action") != "ovfm.Erride" {
|
||||
if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
|
||||
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
|
||||
return http.StatusConflict, errors.New("There is already a file on that path")
|
||||
}
|
||||
|
||||
@@ -287,6 +287,10 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
||||
return http.StatusBadRequest, fm.ErrEmptyPassword
|
||||
}
|
||||
|
||||
if id == c.User.ID && c.User.LockPassword {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
c.User.Password, err = fm.HashPassword(u.Password)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -4941,6 +4941,11 @@
|
||||
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-7.0.0.tgz",
|
||||
"integrity": "sha1-q/sd2CRwZ04DIrU86xqvQSk45L8="
|
||||
},
|
||||
"noty": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/noty/-/noty-3.1.2.tgz",
|
||||
"integrity": "sha512-5Rn8695fMcGTJdn8gIrG0sYXXNIEwc8BFq4EpyHF3rFwoAt7/kgN2lKrIZ0bUxtFZuYFcnfqwU+4uF6vm21bVg=="
|
||||
},
|
||||
"npm-run-path": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"filesize": "^3.5.10",
|
||||
"moment": "^2.18.1",
|
||||
"normalize.css": "^7.0.0",
|
||||
"noty": "^3.1.2",
|
||||
"vue": "^2.3.3",
|
||||
"vue-i18n": "^7.1.0",
|
||||
"vue-router": "^2.7.0",
|
||||
|
||||
@@ -1 +1 @@
|
||||
6a08f1e90218ebb33745a5034efe896329ee9675
|
||||
3b621522d90af10b96208b839db22160799540b3
|
||||
Reference in New Issue
Block a user