Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7e7679002 | ||
|
|
9a829fd594 | ||
|
|
624d61930c | ||
|
|
ee30e7711f | ||
|
|
6e5116aa27 | ||
|
|
34acffbb7b | ||
|
|
eb6f26c191 | ||
|
|
06f3e9744a | ||
|
|
cac5413424 | ||
|
|
037efc2eb8 | ||
|
|
7de22b53b8 | ||
|
|
ae19731015 | ||
|
|
8aa0797019 | ||
|
|
17b3a403a5 | ||
|
|
819d511690 | ||
|
|
30cfd06e3d | ||
|
|
c5558e6e41 | ||
|
|
c3b3099ebb | ||
|
|
b1d47daa69 | ||
|
|
f51e2d5ba1 | ||
|
|
386974657e | ||
|
|
f7858cd719 | ||
|
|
b355a5c058 | ||
|
|
50758b53f4 | ||
|
|
f9902d2bdb | ||
|
|
b1512d2b66 | ||
|
|
1cfd31756d | ||
|
|
9152c77543 | ||
|
|
c236db329f | ||
|
|
bf6e0abd96 | ||
|
|
8d8c756233 | ||
|
|
d5f0471ab7 | ||
|
|
f7aaae3f63 | ||
|
|
2515819026 | ||
|
|
7ad727d27d | ||
|
|
adbff03274 | ||
|
|
6b8a65382f | ||
|
|
4f7e6cbb52 | ||
|
|
40fe081962 | ||
|
|
ebf1325126 | ||
|
|
ea1b9febb7 | ||
|
|
f3edf63fb2 | ||
|
|
f13e7ba940 | ||
|
|
3ebe219e96 | ||
|
|
d838856711 | ||
|
|
610d55c26f | ||
|
|
ac044016ee | ||
|
|
58af3461a8 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"address": "",
|
"address": "",
|
||||||
"database": "/etc/database.db",
|
"database": "/database.db",
|
||||||
"scope": "/srv",
|
"scope": "/srv",
|
||||||
"allowCommands": true,
|
"allowCommands": true,
|
||||||
"allowEdit": true,
|
"allowEdit": true,
|
||||||
|
|||||||
12
Dockerfile
12
Dockerfile
@@ -7,16 +7,16 @@ RUN apk add --no-cache git
|
|||||||
RUN go get ./...
|
RUN go get ./...
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/hacdias/filemanager/cmd/filemanager
|
WORKDIR /go/src/github.com/hacdias/filemanager/cmd/filemanager
|
||||||
RUN go build -ldflags "-X main.version=$(git tag -l --points-at HEAD)"
|
RUN CGO_ENABLED=0 go build -a
|
||||||
RUN mv filemanager /go/bin/filemanager
|
RUN mv filemanager /go/bin/filemanager
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM scratch
|
||||||
COPY --from=0 /go/bin/filemanager /usr/local/bin/filemanager
|
COPY --from=0 /go/bin/filemanager /filemanager
|
||||||
|
|
||||||
VOLUME /srv
|
VOLUME /srv
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
COPY Docker.json /etc/config.json
|
COPY Docker.json /config.json
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/filemanager"]
|
ENTRYPOINT ["/filemanager"]
|
||||||
CMD ["--config", "/etc/config.json"]
|
CMD ["--config", "/config.json"]
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
<meta name="base" content="{{ .BaseURL }}">
|
<meta name="base" content="{{ .BaseURL }}">
|
||||||
<meta name="staticgen" content="{{ .StaticGen }}">
|
<meta name="staticgen" content="{{ .StaticGen }}">
|
||||||
<meta name="noauth" content="{{ .NoAuth }}">
|
<meta name="noauth" content="{{ .NoAuth }}">
|
||||||
|
<meta name="version" content="{{ .Version }}">
|
||||||
|
<meta name="recaptcha" content="{{ .ReCaptchaKey }}">
|
||||||
<title>File Manager</title>
|
<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="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">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||||
@@ -23,6 +25,13 @@
|
|||||||
<!-- 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">
|
||||||
|
|
||||||
|
<script>CSS = "{{ .CSS }}"</script>
|
||||||
|
|
||||||
|
{{ if .ReCaptcha -}}
|
||||||
|
<script src='https://www.google.com/recaptcha/api.js?render=explicit'></script>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<% for (var chunk of webpack.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)$/)) { %>
|
||||||
|
|||||||
@@ -1,18 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view></router-view>
|
<router-view :dependencies="loaded" @update:css="updateCSS" @clean:css="cleanCSS"></router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
mounted: function () {
|
computed: mapState(['recaptcha']),
|
||||||
// Remove loading animation.
|
data () {
|
||||||
let loading = document.getElementById('loading')
|
return {
|
||||||
loading.classList.add('done')
|
loaded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.recaptcha.length === 0) {
|
||||||
|
this.unload()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(function () {
|
let check = () => {
|
||||||
loading.parentNode.removeChild(loading)
|
if (typeof window.grecaptcha === 'undefined') {
|
||||||
}, 200)
|
setTimeout(check, 100)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unload()
|
||||||
|
}
|
||||||
|
|
||||||
|
check()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unload () {
|
||||||
|
this.loaded = true
|
||||||
|
// Remove loading animation.
|
||||||
|
let loading = document.getElementById('loading')
|
||||||
|
loading.classList.add('done')
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
loading.parentNode.removeChild(loading)
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
this.updateCSS()
|
||||||
|
},
|
||||||
|
updateCSS (global = false) {
|
||||||
|
let css = this.$store.state.css
|
||||||
|
|
||||||
|
if (typeof this.$store.state.user.css === 'string' && !global) {
|
||||||
|
css += '\n' + this.$store.state.user.css
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeCSS()
|
||||||
|
|
||||||
|
let style = document.createElement('style')
|
||||||
|
style.title = 'custom-css'
|
||||||
|
style.type = 'text/css'
|
||||||
|
style.appendChild(document.createTextNode(css))
|
||||||
|
document.head.appendChild(style)
|
||||||
|
},
|
||||||
|
removeCSS () {
|
||||||
|
let style = document.querySelector('style[title="custom-css"]')
|
||||||
|
if (style === undefined || style === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
style.parentElement.removeChild(style)
|
||||||
|
},
|
||||||
|
cleanCSS () {
|
||||||
|
this.updateCSS(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="credits">
|
<p class="credits">
|
||||||
<span>{{ $t('sidebar.servedWith') }} <a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a>.</span>
|
<span><a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a> v{{ version }}</span>
|
||||||
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
||||||
</p>
|
</p>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -72,7 +72,7 @@ import auth from '@/utils/auth'
|
|||||||
export default {
|
export default {
|
||||||
name: 'sidebar',
|
name: 'sidebar',
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['user', 'staticGen']),
|
...mapState(['user', 'staticGen', 'version']),
|
||||||
active () {
|
active () {
|
||||||
return this.$store.state.show === 'sidebar'
|
return this.$store.state.show === 'sidebar'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" 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>{{ $t('buttons.switchView') }}</span>
|
<span>{{ $t('buttons.switchView') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState, mapMutations } from 'vuex'
|
||||||
|
import { updateUser } from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'switch-button',
|
name: 'switch-button',
|
||||||
|
computed: {
|
||||||
|
...mapState(['user']),
|
||||||
|
icon: function () {
|
||||||
|
if (this.user.viewMode === 'mosaic') return 'view_list'
|
||||||
|
return 'view_module'
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapMutations(['updateUser']),
|
||||||
change: function (event) {
|
change: function (event) {
|
||||||
// If we are on mobile we should close the dropdown.
|
// If we are on mobile we should close the dropdown.
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|
||||||
let display = 'mosaic'
|
let user = {...this.user}
|
||||||
|
user.viewMode = (this.icon === 'view_list') ? 'list' : 'mosaic'
|
||||||
|
|
||||||
if (this.$store.state.req.display === 'mosaic') {
|
updateUser(user, 'partial').then(() => {
|
||||||
display = 'list'
|
this.updateUser({ viewMode: user.viewMode })
|
||||||
}
|
}).catch(this.$showError)
|
||||||
|
|
||||||
this.$store.commit('listingDisplay', display)
|
|
||||||
let path = this.$store.state.baseURL
|
|
||||||
if (path === '') path = '/'
|
|
||||||
document.cookie = `display=${display}; max-age=31536000; path=${path}`
|
|
||||||
},
|
|
||||||
icon: function () {
|
|
||||||
if (this.$store.state.req.display === 'mosaic') return 'view_list'
|
|
||||||
return 'view_module'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
buttons.done(button)
|
buttons.done(button)
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
this.$store.commit('setSchedule', '')
|
this.$store.commit('setSchedule', '')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" 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="user.viewMode"
|
||||||
@dragenter="dragEnter"
|
@dragenter="dragEnter"
|
||||||
@dragend="dragEnd">
|
@dragend="dragEnd">
|
||||||
<div>
|
<div>
|
||||||
@@ -98,7 +98,7 @@ export default {
|
|||||||
name: 'listing',
|
name: 'listing',
|
||||||
components: { Item },
|
components: { Item },
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['req', 'selected']),
|
...mapState(['req', 'selected', 'user']),
|
||||||
nameSorted () {
|
nameSorted () {
|
||||||
return (this.req.sort === 'name')
|
return (this.req.sort === 'name')
|
||||||
},
|
},
|
||||||
@@ -210,17 +210,13 @@ export default {
|
|||||||
if (this.$store.state.clipboard.key === 'x') {
|
if (this.$store.state.clipboard.key === 'x') {
|
||||||
api.move(items).then(() => {
|
api.move(items).then(() => {
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
}).catch(error => {
|
}).catch(this.$showError)
|
||||||
this.$store.commit('showError', error)
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
api.copy(items).then(() => {
|
api.copy(items).then(() => {
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
}).catch(error => {
|
}).catch(this.$showError)
|
||||||
this.$store.commit('showError', error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
resizeEvent () {
|
resizeEvent () {
|
||||||
// Update the columns size based on the window width.
|
// Update the columns size based on the window width.
|
||||||
@@ -267,7 +263,7 @@ export default {
|
|||||||
.then(req => {
|
.then(req => {
|
||||||
this.checkConflict(files, req.items, base)
|
this.checkConflict(files, req.items, base)
|
||||||
})
|
})
|
||||||
.catch(error => { console.log(error) })
|
.catch(this.$showError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -348,7 +344,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
finish()
|
finish()
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -109,21 +109,24 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(this.$showError)
|
||||||
this.$store.commit('showError', error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
click: function (event) {
|
click: function (event) {
|
||||||
if (this.selectedCount !== 0) event.preventDefault()
|
if (this.selectedCount !== 0) event.preventDefault()
|
||||||
if (this.$store.state.selected.indexOf(this.index) === -1) {
|
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
||||||
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
|
|
||||||
|
|
||||||
this.addSelected(this.index)
|
|
||||||
} else {
|
|
||||||
this.removeSelected(this.index)
|
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) {
|
touchstart (event) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<img v-if="req.type == 'image'" :src="raw()">
|
<img v-if="req.type == 'image'" :src="raw()">
|
||||||
<audio v-else-if="req.type == 'audio'" :src="raw()" controls></audio>
|
<audio v-else-if="req.type == 'audio'" :src="raw()" autoplay controls></audio>
|
||||||
<video v-else-if="req.type == 'video'" :src="raw()" controls>
|
<video v-else-if="req.type == 'video'" :src="raw()" autoplay controls>
|
||||||
Sorry, your browser doesn't support embedded videos,
|
Sorry, your browser doesn't support embedded videos,
|
||||||
but don't worry, you can <a :href="download()">download it</a>
|
but don't worry, you can <a :href="download()">download it</a>
|
||||||
and watch it with your favorite video player!
|
and watch it with your favorite video player!
|
||||||
@@ -75,7 +75,7 @@ export default {
|
|||||||
this.listing = req
|
this.listing = req
|
||||||
this.updateLinks()
|
this.updateLinks()
|
||||||
})
|
})
|
||||||
.catch(error => { console.log(error) })
|
.catch(this.$showError)
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keyup', this.key)
|
window.removeEventListener('keyup', this.key)
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.copy') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.copyMessage') }}</p>
|
<h2>{{ $t('prompts.copy') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<file-list @update:selected="val => dest = val"></file-list>
|
<div class="card-content">
|
||||||
|
<p>{{ $t('prompts.copyMessage') }}</p>
|
||||||
|
<file-list @update:selected="val => dest = val"></file-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="card-action">
|
||||||
<button class="ok"
|
<button class="cancel flat"
|
||||||
@click="copy"
|
|
||||||
:aria-label="$t('buttons.copy')"
|
|
||||||
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
|
||||||
<button class="cancel"
|
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="flat"
|
||||||
|
@click="copy"
|
||||||
|
:aria-label="$t('buttons.copy')"
|
||||||
|
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,7 +60,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
buttons.done('copy')
|
buttons.done('copy')
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.deleteTitle') }}</h3>
|
<div class="card-content">
|
||||||
<p v-show="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
|
<p v-if="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
|
||||||
<p v-show="req.kind === 'listing'">{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
|
<p v-else>{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
|
||||||
<div>
|
</div>
|
||||||
<button @click="submit"
|
<div class="card-action">
|
||||||
:aria-label="$t('buttons.delete')"
|
<button @click="$store.commit('closeHovers')"
|
||||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
class="flat cancel"
|
||||||
<button class="cancel"
|
|
||||||
@click="$store.commit('closeHovers')"
|
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button @click="submit"
|
||||||
|
class="flat"
|
||||||
|
:aria-label="$t('buttons.delete')"
|
||||||
|
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -43,7 +45,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
buttons.done('delete')
|
buttons.done('delete')
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -70,7 +72,7 @@ export default {
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
buttons.done('delete')
|
buttons.done('delete')
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt" id="download">
|
<div class="card floating" id="download">
|
||||||
<h3>{{ $t('prompts.download') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.downloadMessage') }}</p>
|
<h2>{{ $t('prompts.download') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button @click="download('zip')" autofocus>zip</button>
|
<div class="card-content">
|
||||||
<button @click="download('tar')" autofocus>tar</button>
|
<p>{{ $t('prompts.downloadMessage') }}</p>
|
||||||
<button @click="download('targz')" autofocus>tar.gz</button>
|
|
||||||
<button @click="download('tarbz2')" autofocus>tar.bz2</button>
|
<button class="block cancel" @click="download('zip')" autofocus>zip</button>
|
||||||
<button @click="download('tarxz')" autofocus>tar.xz</button>
|
<button class="block cancel" @click="download('tar')" autofocus>tar</button>
|
||||||
|
<button class="block cancel" @click="download('targz')" autofocus>tar.gz</button>
|
||||||
|
<button class="block cancel" @click="download('tarbz2')" autofocus>tar.bz2</button>
|
||||||
|
<button class="block cancel" @click="download('tarxz')" autofocus>tar.xz</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
// so we fetch the data from the previous directory.
|
||||||
api.fetch(url.removeLastDir(this.$route.path))
|
api.fetch(url.removeLastDir(this.$route.path))
|
||||||
.then(this.fillOptions)
|
.then(this.fillOptions)
|
||||||
.catch(this.showError)
|
.catch(this.$showError)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fillOptions (req) {
|
fillOptions (req) {
|
||||||
@@ -96,7 +96,7 @@ export default {
|
|||||||
|
|
||||||
api.fetch(uri)
|
api.fetch(uri)
|
||||||
.then(this.fillOptions)
|
.then(this.fillOptions)
|
||||||
.catch(this.showError)
|
.catch(this.$showError)
|
||||||
},
|
},
|
||||||
touchstart (event) {
|
touchstart (event) {
|
||||||
let url = event.currentTarget.dataset.url
|
let url = event.currentTarget.dataset.url
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt help">
|
<div class="card floating help">
|
||||||
<h3>{{ $t('help.help') }}</h3>
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('help.help') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul>
|
<div class="card-content">
|
||||||
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
|
<ul>
|
||||||
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
|
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
|
||||||
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
|
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
|
||||||
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
|
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
|
||||||
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
|
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
|
||||||
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
|
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
|
||||||
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
|
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
|
||||||
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
|
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
|
||||||
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
|
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
|
||||||
</ul>
|
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="card-action">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
class="ok"
|
class="flat"
|
||||||
:aria-label="$t('buttons.ok')"
|
:aria-label="$t('buttons.ok')"
|
||||||
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.fileInfo') }}</h3>
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('prompts.fileInfo') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p v-show="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
|
<div class="card-content">
|
||||||
|
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
|
||||||
|
|
||||||
<p v-show="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name() }}</p>
|
<p v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name() }}</p>
|
||||||
<p><strong>{{ $t('prompts.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>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime() }}</p>
|
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime() }}</p>
|
||||||
|
|
||||||
<section v-show="dir() && selected.length === 0">
|
<template v-if="dir() && selected.length === 0">
|
||||||
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
|
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
|
||||||
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
|
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
|
||||||
</section>
|
</template>
|
||||||
|
|
||||||
<section v-show="!dir()">
|
<template v-if="!dir()">
|
||||||
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
|
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
|
||||||
<p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
|
<p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
|
||||||
<p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
|
<p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
|
||||||
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
|
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
|
||||||
</section>
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="card-action">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
class="ok"
|
class="flat"
|
||||||
:aria-label="$t('buttons.ok')"
|
:aria-label="$t('buttons.ok')"
|
||||||
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +115,7 @@ export default {
|
|||||||
|
|
||||||
api.checksum(link, hash)
|
api.checksum(link, hash)
|
||||||
.then((hash) => { event.target.innerHTML = hash })
|
.then((hash) => { event.target.innerHTML = hash })
|
||||||
.catch(error => { this.$store.commit('showError', error) })
|
.catch(this.$showError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.move') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.moveMessage') }}</p>
|
<h2>{{ $t('prompts.move') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<file-list @update:selected="val => dest = val"></file-list>
|
<div class="card-content">
|
||||||
|
<file-list @update:selected="val => dest = val"></file-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="card-action">
|
||||||
<button class="ok"
|
<button class="flat cancel"
|
||||||
@click="move"
|
|
||||||
:aria-label="$t('buttons.move')"
|
|
||||||
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
|
||||||
<button class="cancel"
|
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="flat"
|
||||||
|
@click="move"
|
||||||
|
:aria-label="$t('buttons.move')"
|
||||||
|
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,7 +59,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
buttons.done('move')
|
buttons.done('move')
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.newFile') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.newArchetype') }}</p>
|
<h2>{{ $t('prompts.newFile') }}</h2>
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
</div>
|
||||||
<input type="text" @keyup.enter="submit" v-model.trim="archetype">
|
|
||||||
<div>
|
<div class="card-content">
|
||||||
<button class="ok"
|
<p>{{ $t('prompts.newArchetype') }}</p>
|
||||||
@click="submit"
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
:aria-label="$t('buttons.create')"
|
<input type="text" @keyup.enter="submit" v-model.trim="archetype">
|
||||||
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
|
</div>
|
||||||
<button class="cancel"
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button class="flat cancel"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="flat"
|
||||||
|
@click="submit"
|
||||||
|
:aria-label="$t('buttons.create')"
|
||||||
|
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,9 +43,7 @@ export default {
|
|||||||
.then((url) => {
|
.then((url) => {
|
||||||
this.$router.push({ path: url })
|
this.$router.push({ path: url })
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(this.$showError)
|
||||||
this.$store.commit('showError', error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
new (url, type) {
|
new (url, type) {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.newDir') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.newDirMessage') }}</p>
|
<h2>{{ $t('prompts.newDir') }}</h2>
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
</div>
|
||||||
<div>
|
|
||||||
<button class="ok"
|
<div class="card-content">
|
||||||
:aria-label="$t('buttons.create')"
|
<p>{{ $t('prompts.newDirMessage') }}</p>
|
||||||
:title="$t('buttons.create')"
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
@click="submit">{{ $t('buttons.create') }}</button>
|
</div>
|
||||||
<button class="cancel"
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button class="cancel flat"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="flat"
|
||||||
|
:aria-label="$t('buttons.create')"
|
||||||
|
:title="$t('buttons.create')"
|
||||||
|
@click="submit">{{ $t('buttons.create') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -43,7 +49,7 @@ export default {
|
|||||||
|
|
||||||
api.post(uri)
|
api.post(uri)
|
||||||
.then(() => { this.$router.push({ path: uri }) })
|
.then(() => { this.$router.push({ path: uri }) })
|
||||||
.catch(error => { this.$store.commit('showError', error) })
|
.catch(this.$showError)
|
||||||
|
|
||||||
// Close the prompt
|
// Close the prompt
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.newFile') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.newFileMessage') }}</p>
|
<h2>{{ $t('prompts.newFile') }}</h2>
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
</div>
|
||||||
<div>
|
|
||||||
<button class="ok"
|
<div class="card-content">
|
||||||
@click="submit"
|
<p>{{ $t('prompts.newFileMessage') }}</p>
|
||||||
:aria-label="$t('buttons.create')"
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
|
</div>
|
||||||
<button class="cancel"
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button class="cancel flat"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="flat"
|
||||||
|
@click="submit"
|
||||||
|
:aria-label="$t('buttons.create')"
|
||||||
|
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -44,7 +50,7 @@ export default {
|
|||||||
// Create the new file.
|
// Create the new file.
|
||||||
api.post(uri)
|
api.post(uri)
|
||||||
.then(() => { this.$router.push({ path: uri }) })
|
.then(() => { this.$router.push({ path: uri }) })
|
||||||
.catch(error => { this.$store.commit('showError', error) })
|
.catch(this.$showError)
|
||||||
|
|
||||||
// Close the prompt.
|
// Close the prompt.
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
<info v-else-if="showInfo"></info>
|
<info v-else-if="showInfo"></info>
|
||||||
<move v-else-if="showMove"></move>
|
<move v-else-if="showMove"></move>
|
||||||
<copy v-else-if="showCopy"></copy>
|
<copy v-else-if="showCopy"></copy>
|
||||||
<error v-else-if="showError"></error>
|
|
||||||
<success v-else-if="showSuccess"></success>
|
|
||||||
<replace v-else-if="showReplace"></replace>
|
<replace v-else-if="showReplace"></replace>
|
||||||
<schedule v-else-if="show === 'schedule'"></schedule>
|
<schedule v-else-if="show === 'schedule'"></schedule>
|
||||||
<new-archetype v-else-if="show === 'new-archetype'"></new-archetype>
|
<new-archetype v-else-if="show === 'new-archetype'"></new-archetype>
|
||||||
@@ -27,8 +25,6 @@ import Rename from './Rename'
|
|||||||
import Download from './Download'
|
import Download from './Download'
|
||||||
import Move from './Move'
|
import Move from './Move'
|
||||||
import Copy from './Copy'
|
import Copy from './Copy'
|
||||||
import Error from './Error'
|
|
||||||
import Success from './Success'
|
|
||||||
import NewFile from './NewFile'
|
import NewFile from './NewFile'
|
||||||
import NewDir from './NewDir'
|
import NewDir from './NewDir'
|
||||||
import NewArchetype from './NewArchetype'
|
import NewArchetype from './NewArchetype'
|
||||||
@@ -47,9 +43,7 @@ export default {
|
|||||||
NewArchetype,
|
NewArchetype,
|
||||||
Schedule,
|
Schedule,
|
||||||
Rename,
|
Rename,
|
||||||
Error,
|
|
||||||
Download,
|
Download,
|
||||||
Success,
|
|
||||||
Move,
|
Move,
|
||||||
Copy,
|
Copy,
|
||||||
Share,
|
Share,
|
||||||
@@ -70,8 +64,6 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['show', 'plugins']),
|
...mapState(['show', 'plugins']),
|
||||||
showError: function () { return this.show === 'error' },
|
|
||||||
showSuccess: function () { return this.show === 'success' },
|
|
||||||
showInfo: function () { return this.show === 'info' },
|
showInfo: function () { return this.show === 'info' },
|
||||||
showHelp: function () { return this.show === 'help' },
|
showHelp: function () { return this.show === 'help' },
|
||||||
showDelete: function () { return this.show === 'delete' },
|
showDelete: function () { return this.show === 'delete' },
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.rename') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
<h2>{{ $t('prompts.rename') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
<div class="card-content">
|
||||||
<div>
|
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
||||||
<button @click="submit"
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
type="submit"
|
</div>
|
||||||
:aria-label="$t('buttons.rename')"
|
|
||||||
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
<div class="card-action">
|
||||||
<button class="cancel"
|
<button class="cancel flat"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button @click="submit"
|
||||||
|
class="flat"
|
||||||
|
type="submit"
|
||||||
|
:aria-label="$t('buttons.rename')"
|
||||||
|
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -68,7 +74,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$store.commit('showError', error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.replace') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.replaceMessage') }}</p>
|
<h2>{{ $t('prompts.replace') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="card-content">
|
||||||
<button class="ok"
|
<p>{{ $t('prompts.replaceMessage') }}</p>
|
||||||
@click="showConfirm"
|
</div>
|
||||||
:aria-label="$t('buttons.replace')"
|
|
||||||
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
|
<div class="card-action">
|
||||||
<button class="cancel"
|
<button class="flat cancel"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="flat"
|
||||||
|
@click="showConfirm"
|
||||||
|
:aria-label="$t('buttons.replace')"
|
||||||
|
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt">
|
<div class="card floating">
|
||||||
<h3>{{ $t('prompts.schedule') }}</h3>
|
<div class="card-title">
|
||||||
<p>{{ $t('prompts.scheduleMessage') }}</p>
|
<h2>{{ $t('prompts.schedule') }}</h2>
|
||||||
<input autofocus type="datetime-local" v-model="date">
|
</div>
|
||||||
<div>
|
|
||||||
<button class="ok"
|
<div class="card-content">
|
||||||
@click="submit"
|
<p>{{ $t('prompts.scheduleMessage') }}</p>
|
||||||
:aria-label="$t('buttons.schedule')"
|
<input autofocus type="datetime-local" v-model="date">
|
||||||
:title="$t('buttons.schedule')">{{ $t('buttons.schedule') }}</button>
|
</div>
|
||||||
<button class="cancel"
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button class="cancel flat"
|
||||||
@click="close"
|
@click="close"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
|
<button class="falt"
|
||||||
|
@click="submit"
|
||||||
|
:aria-label="$t('buttons.schedule')"
|
||||||
|
:title="$t('buttons.schedule')">{{ $t('buttons.schedule') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,51 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt" id="share">
|
<div class="card floating" id="share">
|
||||||
<h3>{{ $t('buttons.share') }}</h3>
|
<div class="card-title">
|
||||||
<p></p>
|
<h2>{{ $t('buttons.share') }}</h2>
|
||||||
<ul>
|
</div>
|
||||||
<li v-if="!hasPermanent">
|
|
||||||
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li v-for="link in links" :key="link.hash">
|
<div class="card-content">
|
||||||
<a :href="buildLink(link.hash)" target="_blank">
|
<ul>
|
||||||
<template v-if="link.expires">{{ humanTime(link.expireDate) }}</template>
|
<li v-if="!hasPermanent">
|
||||||
<template v-else>{{ $t('permanent') }}</template>
|
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
|
||||||
</a>
|
</li>
|
||||||
|
|
||||||
<button class="action"
|
<li v-for="link in links" :key="link.hash">
|
||||||
@click="deleteLink($event, link)"
|
<a :href="buildLink(link.hash)" target="_blank">
|
||||||
:aria-label="$t('buttons.delete')"
|
<template v-if="link.expires">{{ humanTime(link.expireDate) }}</template>
|
||||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
<template v-else>{{ $t('permanent') }}</template>
|
||||||
|
</a>
|
||||||
|
|
||||||
<button class="action copy"
|
<button class="action"
|
||||||
:data-clipboard-text="buildLink(link.hash)"
|
@click="deleteLink($event, link)"
|
||||||
:aria-label="$t('buttons.copyToClipboard')"
|
:aria-label="$t('buttons.delete')"
|
||||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
<button class="action copy-clipboard"
|
||||||
<input autofocus
|
:data-clipboard-text="buildLink(link.hash)"
|
||||||
type="number"
|
:aria-label="$t('buttons.copyToClipboard')"
|
||||||
max="2147483647"
|
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||||
min="0"
|
</li>
|
||||||
@keyup.enter="submit"
|
|
||||||
v-model.trim="time">
|
|
||||||
<select v-model="unit" :aria-label="$t('time.unit')">
|
|
||||||
<option value="seconds">{{ $t('time.seconds') }}</option>
|
|
||||||
<option value="minutes">{{ $t('time.minutes') }}</option>
|
|
||||||
<option value="hours">{{ $t('time.hours') }}</option>
|
|
||||||
<option value="days">{{ $t('time.days') }}</option>
|
|
||||||
</select>
|
|
||||||
<button class="action"
|
|
||||||
@click="submit"
|
|
||||||
:aria-label="$t('buttons.create')"
|
|
||||||
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div>
|
<li>
|
||||||
<button class="cancel"
|
<input autofocus
|
||||||
|
type="number"
|
||||||
|
max="2147483647"
|
||||||
|
min="0"
|
||||||
|
@keyup.enter="submit"
|
||||||
|
v-model.trim="time">
|
||||||
|
<select v-model="unit" :aria-label="$t('time.unit')">
|
||||||
|
<option value="seconds">{{ $t('time.seconds') }}</option>
|
||||||
|
<option value="minutes">{{ $t('time.minutes') }}</option>
|
||||||
|
<option value="hours">{{ $t('time.hours') }}</option>
|
||||||
|
<option value="days">{{ $t('time.days') }}</option>
|
||||||
|
</select>
|
||||||
|
<button class="action"
|
||||||
|
@click="submit"
|
||||||
|
:aria-label="$t('buttons.create')"
|
||||||
|
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button class="flat"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.close')"
|
:aria-label="$t('buttons.close')"
|
||||||
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
||||||
@@ -54,7 +58,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapMutations } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { getShare, deleteShare, share } from '@/utils/api'
|
import { getShare, deleteShare, share } from '@/utils/api'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import Clipboard from 'clipboard'
|
import Clipboard from 'clipboard'
|
||||||
@@ -101,20 +105,25 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error === 404) return
|
if (error === 404) return
|
||||||
this.showError(error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
mounted () {
|
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: {
|
methods: {
|
||||||
...mapMutations([ 'showError' ]),
|
|
||||||
submit: function (event) {
|
submit: function (event) {
|
||||||
if (!this.time) return
|
if (!this.time) return
|
||||||
|
|
||||||
share(this.url, this.time, this.unit)
|
share(this.url, this.time, this.unit)
|
||||||
.then(result => { this.links.push(result); this.sort() })
|
.then(result => { this.links.push(result); this.sort() })
|
||||||
.catch(error => { this.showError(error) })
|
.catch(this.$showError)
|
||||||
},
|
},
|
||||||
getPermalink (event) {
|
getPermalink (event) {
|
||||||
share(this.url)
|
share(this.url)
|
||||||
@@ -123,7 +132,7 @@ export default {
|
|||||||
this.sort()
|
this.sort()
|
||||||
this.hasPermanent = true
|
this.hasPermanent = true
|
||||||
})
|
})
|
||||||
.catch(error => { this.showError(error) })
|
.catch(this.$showError)
|
||||||
},
|
},
|
||||||
deleteLink (event, link) {
|
deleteLink (event, link) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -132,7 +141,7 @@ export default {
|
|||||||
if (!link.expires) this.hasPermanent = false
|
if (!link.expires) this.hasPermanent = false
|
||||||
this.links = this.links.filter(item => item.hash !== link.hash)
|
this.links = this.links.filter(item => item.hash !== link.hash)
|
||||||
})
|
})
|
||||||
.catch(error => { this.showError(error) })
|
.catch(this.$showError)
|
||||||
},
|
},
|
||||||
humanTime (time) {
|
humanTime (time) {
|
||||||
return moment(time).fromNow()
|
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>
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
body {
|
body {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
background-color: #f8f8f8;
|
background-color: #fafafa;
|
||||||
user-select: none;
|
color: #333333;
|
||||||
color: #212121;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -66,6 +65,53 @@ button:hover {
|
|||||||
background-color: #1E88E5;
|
background-color: #1E88E5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="submit"].block,
|
||||||
|
button.block {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.delete {
|
||||||
|
background: #F44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.delete:hover {
|
||||||
|
background: #D32F2F;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.cancel {
|
||||||
|
background-color: #ECEFF1;
|
||||||
|
color: #37474F;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.cancel:hover {
|
||||||
|
background-color: #e9eaeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.flat,
|
||||||
|
input[type="submit"].flat {
|
||||||
|
color: #1E88E5;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
border: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.flat:hover,
|
||||||
|
input[type="submit"].flat:hover {
|
||||||
|
background: rgba(0,0,0,0.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
button.flat.delete {
|
||||||
|
color: #F44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.flat.cancel {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-only {
|
.mobile-only {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,34 @@
|
|||||||
.dashboard {
|
.dashboard {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
|
||||||
border-radius: .5em;
|
|
||||||
background: #fff;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard a {
|
a {
|
||||||
color: inherit
|
color: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard h1 button {
|
select,
|
||||||
font-size: 0.5em;
|
textarea,
|
||||||
float: right;
|
input[type="text"],
|
||||||
}
|
input[type="password"] {
|
||||||
|
padding: 0.5em 0;
|
||||||
.dashboard table {
|
line-height: 1;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard table th {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #757575;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard table th,
|
|
||||||
.dashboard table td {
|
|
||||||
padding: .5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard table td:last-child {
|
|
||||||
width: 1em
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard > h1:first-of-type {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard form > p:last-child,
|
|
||||||
form.dashboard > p:last-child {
|
|
||||||
text-align: right
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard > *:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard select,
|
|
||||||
.dashboard textarea,
|
|
||||||
.dashboard input[type="text"],
|
|
||||||
.dashboard input[type="password"] {
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1.7;
|
|
||||||
display: block;
|
display: block;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid #dddddd;
|
border-bottom: 1px solid #dddddd;
|
||||||
transition: .2s ease border;
|
transition: .2s ease border;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
line-height: 1.15;
|
||||||
|
padding: .5em;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-family: monospace;
|
||||||
|
min-height: 10em;
|
||||||
|
resize: none;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #locale,
|
.dashboard #locale,
|
||||||
@@ -69,49 +39,32 @@ form.dashboard > p:last-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #locale {
|
.dashboard #locale {
|
||||||
border: 1px solid #dddddd;
|
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard textarea:focus,
|
textarea:focus,
|
||||||
.dashboard textarea:hover,
|
textarea:hover,
|
||||||
.dashboard input[type="text"]:focus,
|
input[type="text"]:focus,
|
||||||
.dashboard input[type="password"]:focus,
|
input[type="password"]:focus,
|
||||||
.dashboard input[type="text"]:hover,
|
input[type="text"]:hover,
|
||||||
.dashboard input[type="password"]:hover {
|
input[type="password"]:hover {
|
||||||
border-color: #2979ff;
|
border-color: #2979ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard input.red {
|
input.red {
|
||||||
border-color: red;
|
border-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard input.green {
|
input.green {
|
||||||
border-color: green;
|
border-color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard button.delete {
|
|
||||||
background: #F44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard button.delete:hover {
|
|
||||||
background: #D32F2F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard textarea {
|
|
||||||
line-height: 1.15;
|
|
||||||
padding: .5em;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
font-family: monospace;
|
|
||||||
min-height: 10em;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard p label {
|
.dashboard p label {
|
||||||
margin-bottom: .2em;
|
margin-bottom: .2em;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
|
color: rgba(0, 0, 0, 0.57);
|
||||||
}
|
}
|
||||||
|
|
||||||
li code,
|
li code,
|
||||||
@@ -131,21 +84,337 @@ p code {
|
|||||||
display: flex;
|
display: flex;
|
||||||
color: rgb(84, 110, 122);
|
color: rgb(84, 110, 122);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 0 0 1em;
|
|
||||||
margin: 0 0 1em;
|
margin: 0 0 1em;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
text-align: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #nav li {
|
.dashboard #nav li {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 0 0 1em;
|
||||||
|
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #nav li:last-child {
|
.dashboard #nav li.active {
|
||||||
text-align: right
|
border-color: #2196f3
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #nav i {
|
.dashboard #nav i {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:last-child {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #757575;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
padding: .5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td.small {
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr>*:first-child {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr>*:last-child {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
margin: .5rem 0 1rem 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.floating {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 99999;
|
||||||
|
max-width: 25em;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 95%;
|
||||||
|
z-index: 99999;
|
||||||
|
animation: .1s show forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card>*>*:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card>*>*:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-title {
|
||||||
|
padding: 1.5em 1em 1em;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-title>*:first-child {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card>div {
|
||||||
|
padding: 1em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card>div:first-child {
|
||||||
|
padding-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card>div:last-child {
|
||||||
|
padding-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-title * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-action {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-content.full {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h3 {
|
||||||
|
color: rgba(0, 0, 0, 0.53);
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 2em 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content table {
|
||||||
|
margin: 0 -1em;
|
||||||
|
width: calc(100% + 2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card code {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#download {
|
||||||
|
max-width: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#share ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#share ul li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#share ul li a {
|
||||||
|
color: #2196F3;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#share ul li .action i {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#share ul li input,
|
||||||
|
.card#share ul li select {
|
||||||
|
padding: .2em;
|
||||||
|
margin-right: .5em;
|
||||||
|
border: 1px solid #dadada;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card#share .action.copy-clipboard.active::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
animation: .1s show forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * *
|
||||||
|
* PROMPT - MOVE *
|
||||||
|
* * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow: auto;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li {
|
||||||
|
width: 100%;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: .2em;
|
||||||
|
padding: .3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li[aria-selected=true] {
|
||||||
|
background: #2196f3 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
transition: .1s ease all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li:hover {
|
||||||
|
background-color: #e9eaeb;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li:before {
|
||||||
|
content: "folder";
|
||||||
|
color: #6f6f6f;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-size: 1.75em;
|
||||||
|
margin-right: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li[aria-selected=true]:before {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help {
|
||||||
|
max-width: 24em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 1em 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes show {
|
||||||
|
0% {
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
1% {
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
display: block;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
border-top: 1px solid rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible:last-of-type {
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible > input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible > label {
|
||||||
|
padding: 1em 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible > label * {
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(0,0,0,0.57);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible > label i {
|
||||||
|
transition: .2s ease transform;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible .collapse {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: .2s ease all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible > input:checked ~ .collapse {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
max-height: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible > input:checked ~ label i {
|
||||||
|
transform: rotate(180deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .collapsible {
|
||||||
|
width: calc(100% + 2em);
|
||||||
|
margin: 0 -1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .collapsible > label {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .collapsible .collapse {
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,8 +122,9 @@ header .search-button {
|
|||||||
#search input {
|
#search input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
line-height: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search #result {
|
#search #result {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
|
|
||||||
#listing.list .item.header {
|
#listing.list .item.header {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
background: #f8f8f8;
|
background: #fafafa;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: calc(100% - 19em);
|
width: calc(100% - 19em);
|
||||||
top: 7em;
|
top: 7em;
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login.recaptcha form {
|
||||||
|
min-width: 304px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login #recaptcha {
|
||||||
|
margin: .5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
#login input {
|
#login input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
.prompt {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.075);
|
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 2em;
|
|
||||||
max-width: 25em;
|
|
||||||
width: 90%;
|
|
||||||
max-height: 95%;
|
|
||||||
z-index: 99999;
|
|
||||||
animation: .1s show forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 9999;
|
|
||||||
animation: .1s show forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt p {
|
|
||||||
font-size: .9em;
|
|
||||||
color: rgba(0, 0, 0, 0.8);
|
|
||||||
margin: .5em 0 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt input:not([type="submit"]) {
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #dadada;
|
|
||||||
line-height: 1;
|
|
||||||
padding: .3em;
|
|
||||||
margin: .3em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt code {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt div:last-of-type {
|
|
||||||
margin-top: 1em;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt .cancel {
|
|
||||||
background-color: #ECEFF1;
|
|
||||||
color: #37474F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt .cancel:hover {
|
|
||||||
background-color: #e9eaeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt.success i,
|
|
||||||
.prompt.error i {
|
|
||||||
color: #F44336;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto .15em;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt.success h3,
|
|
||||||
.prompt.error h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt.error button:not(.cancel) {
|
|
||||||
background-color: #F44336
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt.success i {
|
|
||||||
color: #8BC34A;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt.success button {
|
|
||||||
background-color: #8BC34A;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
|
||||||
* PROMPT - MOVE *
|
|
||||||
* * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
.file-list {
|
|
||||||
max-height: 50vh;
|
|
||||||
overflow: auto;
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list li {
|
|
||||||
width: 100%;
|
|
||||||
user-select: none;
|
|
||||||
border-radius: .2em;
|
|
||||||
padding: .3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list li[aria-selected=true] {
|
|
||||||
background: #2196f3 !important;
|
|
||||||
color: #fff !important;
|
|
||||||
transition: .1s ease all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list li:hover {
|
|
||||||
background-color: #e9eaeb;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list li:before {
|
|
||||||
content: "folder";
|
|
||||||
color: #6f6f6f;
|
|
||||||
vertical-align: middle;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-family: 'Material Icons';
|
|
||||||
font-size: 1.75em;
|
|
||||||
margin-right: .25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list li[aria-selected=true]:before {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#download {
|
|
||||||
max-width: 15em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#download button {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
margin: 0 0 1em;
|
|
||||||
background-color: #ECEFF1;
|
|
||||||
color: #37474F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#download button:last-of-type {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help {
|
|
||||||
max-width: 24em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help ul {
|
|
||||||
padding: 0;
|
|
||||||
margin: 1em 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes show {
|
|
||||||
0% {
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
1% {
|
|
||||||
display: block;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
display: block;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#share ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#share ul li {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#share ul li a {
|
|
||||||
color: #2196F3;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#share ul li .action i {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt#share ul li input,
|
|
||||||
.prompt#share ul li select {
|
|
||||||
padding: .2em;
|
|
||||||
margin-right: .5em;
|
|
||||||
border: 1px solid #dadada;
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@import "~normalize.css/normalize.css";
|
@import "~normalize.css/normalize.css";
|
||||||
|
@import "~noty/lib/noty.css";
|
||||||
@import "./fonts.css";
|
@import "./fonts.css";
|
||||||
@import "./base.css";
|
@import "./base.css";
|
||||||
@import "./header.css";
|
@import "./header.css";
|
||||||
@import "./prompts.css";
|
|
||||||
@import "./listing.css";
|
@import "./listing.css";
|
||||||
@import "./editor.css";
|
@import "./editor.css";
|
||||||
@import "./dashboard.css";
|
@import "./dashboard.css";
|
||||||
@@ -180,6 +180,17 @@
|
|||||||
* PROMPT *
|
* 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 *
|
* FOOTER *
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ buttons:
|
|||||||
publish: Publish
|
publish: Publish
|
||||||
selectMultiple: Select multiple
|
selectMultiple: Select multiple
|
||||||
schedule: Schedule
|
schedule: Schedule
|
||||||
switchView: Swicth view
|
switchView: Switch view
|
||||||
toggleSidebar: Toggle sidebar
|
toggleSidebar: Toggle sidebar
|
||||||
update: Update
|
update: Update
|
||||||
upload: Upload
|
upload: Upload
|
||||||
permalink: Get Permanent Link
|
permalink: Get Permanent Link
|
||||||
|
success:
|
||||||
|
linkCopied: Link copied!
|
||||||
errors:
|
errors:
|
||||||
forbidden: You're not welcome here.
|
forbidden: You're not welcome here.
|
||||||
internal: Something really went wrong.
|
internal: Something really went wrong.
|
||||||
@@ -115,13 +117,14 @@ settings:
|
|||||||
commandsHelp: >
|
commandsHelp: >
|
||||||
Here you can set commands that are executed in the named events. You
|
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
|
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
|
after saving, the environment variable "FILE" will be available with the path
|
||||||
of the file.
|
of the file.
|
||||||
commandsUpdated: Commands updated!
|
commandsUpdated: Commands updated!
|
||||||
customStylesheet: Custom Stylesheet
|
customStylesheet: Custom Stylesheet
|
||||||
examples: Examples
|
examples: Examples
|
||||||
globalSettings: Global Settings
|
globalSettings: Global Settings
|
||||||
language: Language
|
language: Language
|
||||||
|
lockPassword: Prevent the user from changing the password
|
||||||
newPassword: Your new password
|
newPassword: Your new password
|
||||||
newPasswordConfirm: Confirm your new password
|
newPasswordConfirm: Confirm your new password
|
||||||
newUser: New User
|
newUser: New User
|
||||||
@@ -165,7 +168,6 @@ sidebar:
|
|||||||
myFiles: My files
|
myFiles: My files
|
||||||
newFile: New file
|
newFile: New file
|
||||||
newFolder: New folder
|
newFolder: New folder
|
||||||
servedWith: Served with
|
|
||||||
settings: Settings
|
settings: Settings
|
||||||
siteSettings: Site Settings
|
siteSettings: Site Settings
|
||||||
hugoNew: Hugo New
|
hugoNew: Hugo New
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ settings:
|
|||||||
commandsHelp: >
|
commandsHelp: >
|
||||||
Ici vous pouvez définir des commandes qui seront exécutées lors de l'évènement correspondant.
|
Ici vous pouvez définir des commandes qui seront exécutées lors de l'évènement correspondant.
|
||||||
Vous devez indiquer une commande par ligne. Si l'évènement est en rapport avec des fichiers,
|
Vous devez indiquer une commande par ligne. Si l'évènement est en rapport avec des fichiers,
|
||||||
par exemple avant et après enregistrement, la variable d'environement "file" sera disponible
|
par exemple avant et après enregistrement, la variable d'environement "FILE" sera disponible
|
||||||
et contiendra le chemin d'accès vers le fichier.
|
et contiendra le chemin d'accès vers le fichier.
|
||||||
commandsUpdated: Commandes mises à jour !
|
commandsUpdated: Commandes mises à jour !
|
||||||
customStylesheet: Feuille de style personnalisée
|
customStylesheet: Feuille de style personnalisée
|
||||||
@@ -161,7 +161,6 @@ sidebar:
|
|||||||
myFiles: Mes fichiers
|
myFiles: Mes fichiers
|
||||||
newFile: Nouveau fichier
|
newFile: Nouveau fichier
|
||||||
newFolder: Nouveau dossier
|
newFolder: Nouveau dossier
|
||||||
servedWith: Géré avec
|
|
||||||
settings: Paramètres
|
settings: Paramètres
|
||||||
siteSettings: Paramètres du site
|
siteSettings: Paramètres du site
|
||||||
hugoNew: Nouveau Hugo
|
hugoNew: Nouveau Hugo
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ buttons:
|
|||||||
update: 更新
|
update: 更新
|
||||||
upload: アップロード
|
upload: アップロード
|
||||||
permalink: 固定リンク
|
permalink: 固定リンク
|
||||||
|
success:
|
||||||
|
linkCopied: リンクがコピーされました!
|
||||||
errors:
|
errors:
|
||||||
forbidden: アクセスが拒否されました。
|
forbidden: アクセスが拒否されました。
|
||||||
internal: 内部エラーが発生しました。
|
internal: 内部エラーが発生しました。
|
||||||
@@ -116,12 +118,13 @@ settings:
|
|||||||
ここで、名前付きイベントに実行するコマンドを設定することができます。\
|
ここで、名前付きイベントに実行するコマンドを設定することができます。\
|
||||||
一行にコマンド一つを入力してください。\
|
一行にコマンド一つを入力してください。\
|
||||||
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
|
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
|
||||||
環境変数 file はファイルのパスに割り当てられます。"
|
環境変数 FILE はファイルのパスに割り当てられます。"
|
||||||
commandsUpdated: コマンドは更新されました!
|
commandsUpdated: コマンドは更新されました!
|
||||||
customStylesheet: カスタムスタイルシ ート
|
customStylesheet: カスタムスタイルシ ート
|
||||||
examples: 例
|
examples: 例
|
||||||
globalSettings: グローバル設定
|
globalSettings: グローバル設定
|
||||||
language: 言語
|
language: 言語
|
||||||
|
lockPassowrd: 新しいパスワードを変更に禁止
|
||||||
newPassword: 新しいパスワード
|
newPassword: 新しいパスワード
|
||||||
newPasswordConfirm: 新しいパスワードを確認します
|
newPasswordConfirm: 新しいパスワードを確認します
|
||||||
newUser: 新しいユーザー
|
newUser: 新しいユーザー
|
||||||
@@ -165,7 +168,6 @@ sidebar:
|
|||||||
myFiles: 私のファイル
|
myFiles: 私のファイル
|
||||||
newFile: 新しいファイルを作成
|
newFile: 新しいファイルを作成
|
||||||
newFolder: 新しいフォルダを作成
|
newFolder: 新しいフォルダを作成
|
||||||
servedWith: サービス提供者
|
|
||||||
settings: 設定
|
settings: 設定
|
||||||
siteSettings: サイト設定
|
siteSettings: サイト設定
|
||||||
hugoNew: Hugo New
|
hugoNew: Hugo New
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ buttons:
|
|||||||
update: Atualizar
|
update: Atualizar
|
||||||
upload: Enviar
|
upload: Enviar
|
||||||
permalink: Obter link permanente
|
permalink: Obter link permanente
|
||||||
|
success:
|
||||||
|
linkCopied: Link copiado!
|
||||||
errors:
|
errors:
|
||||||
forbidden: Tu não és bem-vindo aqui.
|
forbidden: Tu não és bem-vindo aqui.
|
||||||
internal: Algo correu bastante mal.
|
internal: Algo correu bastante mal.
|
||||||
@@ -137,12 +139,13 @@ settings:
|
|||||||
Pode definir um conjunto de comandos a executar em determiandos eventos.
|
Pode definir um conjunto de comandos a executar em determiandos eventos.
|
||||||
Deve escrever um comando por linha. Se o evento estiver relacionado com ficheiros,
|
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
|
como antes e depois de guardar, irá existir uma variável de ambiente denominada
|
||||||
"file" com o caminho do ficheiro.
|
"FILE" com o caminho do ficheiro.
|
||||||
commandsUpdated: Comandos atualizados!
|
commandsUpdated: Comandos atualizados!
|
||||||
customStylesheet: Estilos Personalizados
|
customStylesheet: Estilos Personalizados
|
||||||
examples: Exemplos
|
examples: Exemplos
|
||||||
globalSettings: Configurações Globais
|
globalSettings: Configurações Globais
|
||||||
language: Linguagem
|
language: Linguagem
|
||||||
|
lockPassword: Não permitir que o utilizador altere a palavra-passe
|
||||||
newPassword: Nova palavra-passe
|
newPassword: Nova palavra-passe
|
||||||
newPasswordConfirm: Confirme a nova palavra-passe
|
newPasswordConfirm: Confirme a nova palavra-passe
|
||||||
newUser: Novo Utilizador
|
newUser: Novo Utilizador
|
||||||
@@ -190,7 +193,6 @@ sidebar:
|
|||||||
newFile: Novo ficheiro
|
newFile: Novo ficheiro
|
||||||
newFolder: Nova pasta
|
newFolder: Nova pasta
|
||||||
preview: Pré-visualizar
|
preview: Pré-visualizar
|
||||||
servedWith: Servido com
|
|
||||||
settings: Configurações
|
settings: Configurações
|
||||||
siteSettings: Configurações do Site
|
siteSettings: Configurações do Site
|
||||||
time:
|
time:
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ buttons:
|
|||||||
update: 更新
|
update: 更新
|
||||||
upload: 上传
|
upload: 上传
|
||||||
permalink: 获取永久链接
|
permalink: 获取永久链接
|
||||||
|
success:
|
||||||
|
linkCopied: 链接已复制!
|
||||||
errors:
|
errors:
|
||||||
forbidden: 你被禁止访问。
|
forbidden: 你被禁止访问。
|
||||||
internal: 内部出现麻烦了。
|
internal: 内部出现麻烦了。
|
||||||
@@ -115,12 +117,13 @@ settings:
|
|||||||
commandsHelp: "\
|
commandsHelp: "\
|
||||||
在这里,您可以设置在指定事件下执行的命令,一行一条。\
|
在这里,您可以设置在指定事件下执行的命令,一行一条。\
|
||||||
若事件与文件相关,如“在保存文件前”,\
|
若事件与文件相关,如“在保存文件前”,\
|
||||||
则文件的路径会被赋值给环境变量 \"file\"。"
|
则文件的路径会被赋值给环境变量 \"FILE\"。"
|
||||||
commandsUpdated: 命令已更新!
|
commandsUpdated: 命令已更新!
|
||||||
customStylesheet: 自定义样式表
|
customStylesheet: 自定义样式表
|
||||||
examples: 例子
|
examples: 例子
|
||||||
globalSettings: 全局设置
|
globalSettings: 全局设置
|
||||||
language: 语言
|
language: 语言
|
||||||
|
lockPassowrd: 禁止用户修改密码
|
||||||
newPassword: 您的新密码
|
newPassword: 您的新密码
|
||||||
newPasswordConfirm: 重输一遍新密码
|
newPasswordConfirm: 重输一遍新密码
|
||||||
newUser: 新建用户
|
newUser: 新建用户
|
||||||
@@ -163,7 +166,6 @@ sidebar:
|
|||||||
myFiles: 我的文件
|
myFiles: 我的文件
|
||||||
newFile: 新建文件
|
newFile: 新建文件
|
||||||
newFolder: 新建文件夹
|
newFolder: 新建文件夹
|
||||||
servedWith: '服务提供者:'
|
|
||||||
settings: 设置
|
settings: 设置
|
||||||
siteSettings: 网站设置
|
siteSettings: 网站设置
|
||||||
hugoNew: Hugo New
|
hugoNew: Hugo New
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ buttons:
|
|||||||
close: 關閉
|
close: 關閉
|
||||||
copy: 複製
|
copy: 複製
|
||||||
copyFile: 複製檔案
|
copyFile: 複製檔案
|
||||||
copyToClipboard: 複製到剪貼板
|
copyToClipboard: 複製到剪貼簿
|
||||||
create: 建立
|
create: 建立
|
||||||
delete: 刪除
|
delete: 刪除
|
||||||
download: 下載
|
download: 下載
|
||||||
@@ -31,8 +31,10 @@ buttons:
|
|||||||
update: 更新
|
update: 更新
|
||||||
upload: 上傳
|
upload: 上傳
|
||||||
permalink: 獲取永久連結
|
permalink: 獲取永久連結
|
||||||
|
success:
|
||||||
|
linkCopied: 連結已複製!
|
||||||
errors:
|
errors:
|
||||||
forbidden: 你被禁止訪問。
|
forbidden: 你被禁止存取。
|
||||||
internal: 內部出現麻煩了。
|
internal: 內部出現麻煩了。
|
||||||
notFound: 找不到檔案。
|
notFound: 找不到檔案。
|
||||||
files:
|
files:
|
||||||
@@ -45,7 +47,7 @@ files:
|
|||||||
lastModified: 最後修改
|
lastModified: 最後修改
|
||||||
loading: 讀取中...
|
loading: 讀取中...
|
||||||
lonely: 這裡沒有任何檔案...
|
lonely: 這裡沒有任何檔案...
|
||||||
metadata: 中繼資料
|
metadata: 詮釋資料
|
||||||
multipleSelectionEnabled: 多選模式已開啟
|
multipleSelectionEnabled: 多選模式已開啟
|
||||||
name: 名稱
|
name: 名稱
|
||||||
size: 大小
|
size: 大小
|
||||||
@@ -100,70 +102,70 @@ prompts:
|
|||||||
show: 顯示
|
show: 顯示
|
||||||
size: 大小
|
size: 大小
|
||||||
schedule: 計畫
|
schedule: 計畫
|
||||||
scheduleMessage: 請選擇發佈這篇帖子的日期。
|
scheduleMessage: 請選擇發佈這篇貼文的日期。
|
||||||
newArchetype: 建立一個基於原型的新帖子。您的檔案將會建立在內容資料夾中。
|
newArchetype: 建立一個基於原型的新貼文。您的檔案將會建立在內容資料夾中。
|
||||||
settings:
|
settings:
|
||||||
admin: 管理員
|
admin: 管理員
|
||||||
administrator: 管理員
|
administrator: 管理員
|
||||||
allowCommands: 執行命令
|
allowCommands: 執行命令
|
||||||
allowEdit: 編輯、重命名或刪除檔案/目錄
|
allowEdit: 編輯、重命名或刪除檔案/目錄
|
||||||
allowNew: 創建新檔案和目錄
|
allowNew: 創建新檔案和目錄
|
||||||
allowPublish: 發佈新的帖子與頁面
|
allowPublish: 發佈新的貼文與頁面
|
||||||
avoidChanges: '(留空以避免更改)'
|
avoidChanges: '(留空以避免更改)'
|
||||||
changePassword: 更改密碼
|
changePassword: 更改密碼
|
||||||
commands: 命令
|
commands: 命令
|
||||||
commandsHelp: "\
|
commandsHelp: "\
|
||||||
在這裡,您可以設定在指定事件下執行的命令,一行一條。\
|
在這裡,您可以設定在指定事件下執行的命令,一行一條。\
|
||||||
若事件與檔案相關,如“在保存檔案前”,\
|
若事件與檔案相關,如“在保存檔案前”,\
|
||||||
則檔案的路徑會被賦值給環境變數 \"file\"。"
|
則檔案的路徑會被賦值給環境變數 \"FILE\"。"
|
||||||
commandsUpdated: 命令已更新!
|
commandsUpdated: 命令已更新!
|
||||||
customStylesheet: 自定義樣式表
|
customStylesheet: 自定義樣式表
|
||||||
examples: 例子
|
examples: 範例
|
||||||
globalSettings: 全域設定
|
globalSettings: 全域設定
|
||||||
language: 語言
|
language: 語言
|
||||||
|
lockPassword: 禁止使用者修改密碼
|
||||||
newPassword: 您的新密碼
|
newPassword: 您的新密碼
|
||||||
newPasswordConfirm: 重輸一遍新密碼
|
newPasswordConfirm: 重輸一遍新密碼
|
||||||
newUser: 建立用戶
|
newUser: 建立使用者
|
||||||
password: 密碼
|
password: 密碼
|
||||||
passwordUpdated: 密碼已更新!
|
passwordUpdated: 密碼已更新!
|
||||||
permissions: 權限
|
permissions: 權限
|
||||||
permissionsHelp: "\
|
permissionsHelp: "\
|
||||||
您可以將該用戶設置為管理員,也可以單獨選擇各項權限。\
|
您可以將該使用者設置為管理員,也可以單獨選擇各項權限。\
|
||||||
如果選擇了“管理員”,則其他的選項會被自動勾上,\
|
如果選擇了“管理員”,則其他的選項會被自動勾上,\
|
||||||
同時該用戶可以管理其他用戶。"
|
同時該使用者可以管理其他使用者。"
|
||||||
profileSettings: 設定檔設定
|
profileSettings: 設定檔設定
|
||||||
ruleExample1: "\
|
ruleExample1: "\
|
||||||
封鎖用戶訪問所有資料夾下任何以 . 開頭的檔案\
|
封鎖使用者存取所有資料夾下任何以 . 開頭的檔案\
|
||||||
(隱藏文件, 例如: .git, .gitignore)。"
|
(隱藏文件, 例如: .git, .gitignore)。"
|
||||||
ruleExample2: 封鎖用戶訪問其目錄範圍的根目錄下名為 Caddyfile 的檔案。
|
ruleExample2: 封鎖使用者存取其目錄範圍的根目錄下名為 Caddyfile 的檔案。
|
||||||
rules: 規則
|
rules: 規則
|
||||||
rulesHelp1: "\
|
rulesHelp1: "\
|
||||||
您可以為該用戶製定一組黑名單或白名單式的規則,\
|
您可以為該使用者製定一組黑名單或白名單式的規則,\
|
||||||
被屏蔽的檔案將不會顯示在清單中,用戶也無權限訪問,\
|
被屏蔽的檔案將不會顯示在清單中,使用者也無權限存取,\
|
||||||
支持相對於目錄範圍的路徑。"
|
支持相對於目錄範圍的路徑。"
|
||||||
rulesHelp2: "\
|
rulesHelp2: "\
|
||||||
每行一條規則,且必須以關鍵字 {0} 或 {1} 開頭。\
|
每行一條規則,且必須以關鍵字 {0} 或 {1} 開頭。\
|
||||||
如要使用規則運算式,請在加上 {2} 之後再附上運算式或路徑。"
|
如要使用規則運算式,請在加上 {2} 之後再附上運算式或路徑。"
|
||||||
scope: 目錄範圍
|
scope: 目錄範圍
|
||||||
settingsUpdated: 設定已更新!
|
settingsUpdated: 設定已更新!
|
||||||
user: 用戶
|
user: 使用者
|
||||||
userCommands: 用戶命令
|
userCommands: 使用者命令
|
||||||
userCommandsHelp: "\
|
userCommandsHelp: "\
|
||||||
指定該用戶可以執行的命令,用空格分隔。\
|
指定該使用者可以執行的命令,用空格分隔。\
|
||||||
例如:"
|
例如:"
|
||||||
userCreated: 用戶已建立!
|
userCreated: 使用者已建立!
|
||||||
userDeleted: 用戶已刪除!
|
userDeleted: 使用者已刪除!
|
||||||
userManagement: 用戶管理
|
userManagement: 使用者管理
|
||||||
username: 用戶名
|
username: 使用者名稱
|
||||||
users: 用戶
|
users: 使用者
|
||||||
userUpdated: 用戶已更新!
|
userUpdated: 使用者已更新!
|
||||||
sidebar:
|
sidebar:
|
||||||
help: 幫助
|
help: 幫助
|
||||||
logout: 登出
|
logout: 登出
|
||||||
myFiles: 我的檔案
|
myFiles: 我的檔案
|
||||||
newFile: 建立檔案
|
newFile: 建立檔案
|
||||||
newFolder: 建立資料夾
|
newFolder: 建立資料夾
|
||||||
servedWith: '服務提供者:'
|
|
||||||
settings: 設定
|
settings: 設定
|
||||||
siteSettings: 網站設定
|
siteSettings: 網站設定
|
||||||
hugoNew: Hugo New
|
hugoNew: Hugo New
|
||||||
@@ -187,9 +189,10 @@ languages:
|
|||||||
pt: Português
|
pt: Português
|
||||||
ja: 日本語
|
ja: 日本語
|
||||||
zhCN: 中文 (简体)
|
zhCN: 中文 (简体)
|
||||||
|
zhTW: 中文 (繁體)
|
||||||
time:
|
time:
|
||||||
unit: 時間單位
|
unit: 時間單位
|
||||||
seconds: 秒
|
seconds: 秒
|
||||||
minutes: 分鐘
|
minutes: 分鐘
|
||||||
hours: 小時
|
hours: 小時
|
||||||
days: 天
|
days: 天
|
||||||
|
|||||||
@@ -3,9 +3,46 @@ import App from './App'
|
|||||||
import store from './store'
|
import store from './store'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
|
import Noty from 'noty'
|
||||||
|
|
||||||
Vue.config.productionTip = true
|
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) {
|
||||||
|
let n = new Noty(Object.assign({}, notyDefault, {
|
||||||
|
text: error,
|
||||||
|
type: 'error',
|
||||||
|
timeout: null,
|
||||||
|
buttons: [
|
||||||
|
Noty.button(i18n.t('buttons.reportIssue'), '', 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 */
|
/* eslint-disable no-new */
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import Router from 'vue-router'
|
|||||||
import Login from '@/views/Login'
|
import Login from '@/views/Login'
|
||||||
import Layout from '@/views/Layout'
|
import Layout from '@/views/Layout'
|
||||||
import Files from '@/views/Files'
|
import Files from '@/views/Files'
|
||||||
import Users from '@/views/Users'
|
import Users from '@/views/Settings/Users'
|
||||||
import User from '@/views/User'
|
import User from '@/views/Settings/User'
|
||||||
import GlobalSettings from '@/views/GlobalSettings'
|
import Settings from '@/views/Settings'
|
||||||
import ProfileSettings from '@/views/ProfileSettings'
|
import GlobalSettings from '@/views/settings/Global'
|
||||||
|
import ProfileSettings from '@/views/settings/Profile'
|
||||||
import Error403 from '@/views/errors/403'
|
import Error403 from '@/views/errors/403'
|
||||||
import Error404 from '@/views/errors/404'
|
import Error404 from '@/views/errors/404'
|
||||||
import Error500 from '@/views/errors/500'
|
import Error500 from '@/views/errors/500'
|
||||||
@@ -49,22 +50,41 @@ const router = new Router({
|
|||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
|
component: Settings,
|
||||||
redirect: {
|
redirect: {
|
||||||
path: '/settings/profile'
|
path: '/settings/profile'
|
||||||
}
|
},
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: '/settings/profile',
|
path: '/settings/profile',
|
||||||
name: 'Profile Settings',
|
name: 'Profile Settings',
|
||||||
component: ProfileSettings
|
component: ProfileSettings
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/global',
|
path: '/settings/global',
|
||||||
name: 'Global Settings',
|
name: 'Global Settings',
|
||||||
component: GlobalSettings,
|
component: GlobalSettings,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAdmin: true
|
requiresAdmin: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/users',
|
||||||
|
name: 'Users',
|
||||||
|
component: Users,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/users/*',
|
||||||
|
name: 'User',
|
||||||
|
component: User,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/403',
|
path: '/403',
|
||||||
@@ -81,22 +101,6 @@ const router = new Router({
|
|||||||
name: 'Internal Server Error',
|
name: 'Internal Server Error',
|
||||||
component: Error500
|
component: Error500
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/users',
|
|
||||||
name: 'Users',
|
|
||||||
component: Users,
|
|
||||||
meta: {
|
|
||||||
requiresAdmin: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/users/*',
|
|
||||||
name: 'User',
|
|
||||||
component: User,
|
|
||||||
meta: {
|
|
||||||
requiresAdmin: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/files',
|
path: '/files',
|
||||||
redirect: {
|
redirect: {
|
||||||
|
|||||||
@@ -12,9 +12,16 @@ const state = {
|
|||||||
key: '',
|
key: '',
|
||||||
items: []
|
items: []
|
||||||
},
|
},
|
||||||
|
css: (() => {
|
||||||
|
let css = window.CSS
|
||||||
|
window.CSS = null
|
||||||
|
return css
|
||||||
|
})(),
|
||||||
|
recaptcha: document.querySelector('meta[name="recaptcha"]').getAttribute('content'),
|
||||||
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
||||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||||
noAuth: (document.querySelector('meta[name="noauth"]').getAttribute('content') === 'true'),
|
noAuth: (document.querySelector('meta[name="noauth"]').getAttribute('content') === 'true'),
|
||||||
|
version: document.querySelector('meta[name="version"]').getAttribute('content'),
|
||||||
jwt: '',
|
jwt: '',
|
||||||
progress: 0,
|
progress: 0,
|
||||||
schedule: '',
|
schedule: '',
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const mutations = {
|
|||||||
i18n.locale = value.locale
|
i18n.locale = value.locale
|
||||||
state.user = value
|
state.user = value
|
||||||
},
|
},
|
||||||
|
setCSS: (state, value) => (state.css = 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)),
|
||||||
@@ -45,8 +46,12 @@ const mutations = {
|
|||||||
resetSelected: (state) => {
|
resetSelected: (state) => {
|
||||||
state.selected = []
|
state.selected = []
|
||||||
},
|
},
|
||||||
listingDisplay: (state, value) => {
|
updateUser: (state, value) => {
|
||||||
state.req.display = value
|
if (typeof value !== 'object') return
|
||||||
|
|
||||||
|
for (let field in value) {
|
||||||
|
state.user[field] = value[field]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateRequest: (state, value) => {
|
updateRequest: (state, value) => {
|
||||||
state.req = value
|
state.req = value
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ function loggedIn () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function login (user, password) {
|
function login (user, password, captcha) {
|
||||||
let data = {username: user, password: password}
|
let data = {username: user, password: password, recaptcha: captcha}
|
||||||
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/auth/get`, true)
|
request.open('POST', `${store.state.baseURL}/api/auth/get`, true)
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
<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="saveStaticGen" v-if="$store.state.staticGen.length > 0">
|
|
||||||
<h2>{{ capitalize($store.state.staticGen) }}</h2>
|
|
||||||
|
|
||||||
<p v-for="field in staticGen" :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>
|
|
||||||
|
|
||||||
<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: [],
|
|
||||||
staticGen: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState([ 'user' ])
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
getSettings()
|
|
||||||
.then(settings => {
|
|
||||||
if (this.$store.state.staticGen.length > 0) {
|
|
||||||
this.parseStaticGen(settings.staticGen)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) })
|
|
||||||
},
|
|
||||||
saveStaticGen (event) {
|
|
||||||
event.preventDefault()
|
|
||||||
let staticGen = {}
|
|
||||||
|
|
||||||
for (let field of this.staticGen) {
|
|
||||||
staticGen[field.variable] = field.value
|
|
||||||
|
|
||||||
if (field.original === 'array') {
|
|
||||||
let val = field.value.split(' ')
|
|
||||||
if (val[0] === '') {
|
|
||||||
val.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
staticGen[field.variable] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSettings(staticGen, 'staticGen')
|
|
||||||
.then(() => { this.showSuccess(this.$t('settings.settingsUpdated')) })
|
|
||||||
.catch(error => { this.showError(error) })
|
|
||||||
},
|
|
||||||
parseStaticGen (staticgen) {
|
|
||||||
for (let option of staticgen) {
|
|
||||||
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(' ')
|
|
||||||
|
|
||||||
this.staticGen.push(field)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'boolean':
|
|
||||||
field.type = 'checkbox'
|
|
||||||
field.original = 'boolean'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
this.staticGen.push(field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<site-header></site-header>
|
<site-header></site-header>
|
||||||
<sidebar></sidebar>
|
<sidebar></sidebar>
|
||||||
<main>
|
<main>
|
||||||
<router-view v-on:css-updated="updateCSS"></router-view>
|
<router-view @css="$emit('update:css')"></router-view>
|
||||||
</main>
|
</main>
|
||||||
<prompts></prompts>
|
<prompts></prompts>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,23 +34,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.updateCSS()
|
this.$emit('update:css')
|
||||||
},
|
},
|
||||||
methods: {
|
beforeDestroy () {
|
||||||
updateCSS () {
|
this.$emit('clean:css')
|
||||||
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>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="login">
|
<div id="login" :class="{ recaptcha: recaptcha.length > 0 }">
|
||||||
<form @submit="submit">
|
<form @submit="submit">
|
||||||
<img src="../assets/logo.svg" alt="File Manager">
|
<img src="../assets/logo.svg" alt="File Manager">
|
||||||
<h1>File Manager</h1>
|
<h1>File Manager</h1>
|
||||||
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
|
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
|
||||||
<input type="text" v-model="username" :placeholder="$t('login.username')">
|
<input type="text" v-model="username" :placeholder="$t('login.username')">
|
||||||
<input type="password" v-model="password" :placeholder="$t('login.password')">
|
<input type="password" v-model="password" :placeholder="$t('login.password')">
|
||||||
|
<div v-if="recaptcha.length" id="recaptcha"></div>
|
||||||
<input type="submit" :value="$t('login.submit')">
|
<input type="submit" :value="$t('login.submit')">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,9 +14,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import auth from '@/utils/auth'
|
import auth from '@/utils/auth'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'login',
|
name: 'login',
|
||||||
|
props: ['dependencies'],
|
||||||
|
computed: mapState(['recaptcha']),
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
wrong: false,
|
wrong: false,
|
||||||
@@ -23,8 +27,23 @@ export default {
|
|||||||
password: ''
|
password: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.dependencies) this.setup()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
dependencies: function (val) {
|
||||||
|
if (val) this.setup()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit: function (event) {
|
setup () {
|
||||||
|
if (this.recaptcha.length === 0) return
|
||||||
|
|
||||||
|
window.grecaptcha.render('recaptcha', {
|
||||||
|
sitekey: this.recaptcha
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submit (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
@@ -33,7 +52,17 @@ export default {
|
|||||||
redirect = '/files/'
|
redirect = '/files/'
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.login(this.username, this.password)
|
let captcha = ''
|
||||||
|
if (this.recaptcha.length > 0) {
|
||||||
|
captcha = window.grecaptcha.getResponse()
|
||||||
|
|
||||||
|
if (captcha === '') {
|
||||||
|
this.wrong = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.login(this.username, this.password, captcha)
|
||||||
.then(() => { this.$router.push({ path: redirect }) })
|
.then(() => { this.$router.push({ path: redirect }) })
|
||||||
.catch(() => { this.wrong = true })
|
.catch(() => { this.wrong = true })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
<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>
|
|
||||||
20
assets/src/views/Settings.vue
Normal file
20
assets/src/views/Settings.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<ul id="nav" v-if="user.admin">
|
||||||
|
<li :class="{ active: $route.path === '/settings/profile' }"><router-link to="/settings/profile">{{ $t('settings.profileSettings') }}</router-link></li>
|
||||||
|
<li :class="{ active: $route.path === '/settings/global' }"><router-link to="/settings/global">{{ $t('settings.globalSettings') }}</router-link></li>
|
||||||
|
<li :class="{ active: $route.path === '/settings/users' }"><router-link to="/settings/users">{{ $t('settings.userManagement') }}</router-link></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<router-view @css="$emit('css')"></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'settings',
|
||||||
|
computed: mapState([ 'user' ])
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="dashboard">
|
|
||||||
<ul id="nav">
|
|
||||||
<li>
|
|
||||||
<router-link to="/settings/global">
|
|
||||||
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.globalSettings') }}
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<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 * as 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>
|
|
||||||
187
assets/src/views/settings/Global.vue
Normal file
187
assets/src/views/settings/Global.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<form class="card" v-if="staticGen.length" @submit.prevent="saveStaticGen">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ capitalize($store.state.staticGen) }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<p v-for="field in staticGen" :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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<input class="flat" type="submit" :value="$t('buttons.update')">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="card" @submit.prevent="saveCSS">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('settings.customStylesheet') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<textarea v-model="css"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<input class="flat" type="submit" :value="$t('buttons.update')">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="card" @submit.prevent="saveCommands">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('settings.commands') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="small">{{ $t('settings.commandsHelp') }}</p>
|
||||||
|
|
||||||
|
<div v-for="command in commands" :key="command.name" class="collapsible">
|
||||||
|
<input :id="command.name" type="checkbox">
|
||||||
|
<label :for="command.name">
|
||||||
|
<p>{{ capitalize(command.name) }}</p>
|
||||||
|
<i class="material-icons">arrow_drop_down</i>
|
||||||
|
</label>
|
||||||
|
<div class="collapse">
|
||||||
|
<textarea v-model.trim="command.value"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<input class="flat" type="submit" :value="$t('buttons.update')">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import { getSettings, updateSettings } from '@/utils/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'settings',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
commands: [],
|
||||||
|
staticGen: [],
|
||||||
|
css: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState([ 'user' ])
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
getSettings()
|
||||||
|
.then(settings => {
|
||||||
|
if (this.$store.state.staticGen.length > 0) {
|
||||||
|
this.parseStaticGen(settings.staticGen)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in settings.commands) {
|
||||||
|
this.commands.push({
|
||||||
|
name: key,
|
||||||
|
value: settings.commands[key].join('\n')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.css = settings.css
|
||||||
|
})
|
||||||
|
.catch(this.$showError)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
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) {
|
||||||
|
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(this.$showError)
|
||||||
|
},
|
||||||
|
saveCSS (event) {
|
||||||
|
updateSettings(this.css, 'css')
|
||||||
|
.then(() => {
|
||||||
|
this.$showSuccess(this.$t('settings.settingsUpdated'))
|
||||||
|
this.$store.commit('setCSS', this.css)
|
||||||
|
this.$emit('css')
|
||||||
|
})
|
||||||
|
.catch(this.$showError)
|
||||||
|
},
|
||||||
|
saveStaticGen (event) {
|
||||||
|
let staticGen = {}
|
||||||
|
|
||||||
|
for (let field of this.staticGen) {
|
||||||
|
staticGen[field.variable] = field.value
|
||||||
|
|
||||||
|
if (field.original === 'array') {
|
||||||
|
let val = field.value.split(' ')
|
||||||
|
if (val[0] === '') {
|
||||||
|
val.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
staticGen[field.variable] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettings(staticGen, 'staticGen')
|
||||||
|
.then(() => { this.$showSuccess(this.$t('settings.settingsUpdated')) })
|
||||||
|
.catch(this.$showError)
|
||||||
|
},
|
||||||
|
parseStaticGen (staticgen) {
|
||||||
|
for (let option of staticgen) {
|
||||||
|
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(' ')
|
||||||
|
|
||||||
|
this.staticGen.push(field)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'boolean':
|
||||||
|
field.type = 'checkbox'
|
||||||
|
field.original = 'boolean'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
this.staticGen.push(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
109
assets/src/views/settings/Profile.vue
Normal file
109
assets/src/views/settings/Profile.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<form class="card" @submit="updateSettings">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('settings.profileSettings') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<input class="flat" type="submit" :value="$t('buttons.update')">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="card" v-if="!user.lockPassword" @submit="updatePassword">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('settings.changePassword') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<input class="flat" type="submit" :value="$t('buttons.update')">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } 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: {
|
||||||
|
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.$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')
|
||||||
|
this.$showSuccess(this.$t('settings.settingsUpdated'))
|
||||||
|
}).catch(e => {
|
||||||
|
this.$showError(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,77 +1,81 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<form @submit="save" class="dashboard">
|
<form @submit="save" class="card">
|
||||||
<ul id="nav">
|
<div class="card-title">
|
||||||
<li>
|
<h2 v-if="id === 0">{{ $t('settings.newUser') }}</h2>
|
||||||
<router-link to="/users">
|
<h2 v-else>{{ $t('settings.user') }} {{ username }}</h2>
|
||||||
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.userManagement') }}
|
</div>
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h1 v-if="id === 0">{{ $t('settings.newUser') }}</h1>
|
<div class="card-content">
|
||||||
<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="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="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="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
|
||||||
<p>
|
<p>
|
||||||
<label for="locale">{{ $t('settings.language') }}</label>
|
<label for="locale">{{ $t('settings.language') }}</label>
|
||||||
<languages id="locale" :selected.sync="locale"></languages>
|
<languages id="locale" :selected.sync="locale"></languages>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>{{ $t('settings.permissions') }}</h2>
|
<p><input type="checkbox" :disabled="admin" v-model="lockPassword"> {{ $t('settings.lockPassword') }}</p>
|
||||||
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
|
|
||||||
|
|
||||||
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
<h3>{{ $t('settings.permissions') }}</h3>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
<p class="small">{{ $t('settings.permissionsHelp') }}</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-show="$store.state.staticGen.length"><input type="checkbox" :disabled="admin" v-model="allowPublish"> {{ $t('settings.allowPublish') }}</p>
|
|
||||||
|
|
||||||
<h3>{{ $t('settings.userCommands') }}</h3>
|
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
||||||
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
|
||||||
<input type="text" v-model.trim="commands">
|
<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-show="$store.state.staticGen.length"><input type="checkbox" :disabled="admin" v-model="allowPublish"> {{ $t('settings.allowPublish') }}</p>
|
||||||
|
|
||||||
<h2>{{ $t('settings.rules') }}</h2>
|
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||||
|
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||||
|
<input type="text" v-model.trim="commands">
|
||||||
|
|
||||||
<p class="small">{{ $t('settings.rulesHelp1') }}</p>
|
<h3>{{ $t('settings.rules') }}</h3>
|
||||||
|
|
||||||
<i18n path="settings.rulesHelp2" tag="p" class="small">
|
<p class="small">{{ $t('settings.rulesHelp1') }}</p>
|
||||||
<code>allow</code><code>disallow</code><code>regex</code>
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<p class="small"><strong>{{ $t('settings.examples') }}</strong></p>
|
<i18n path="settings.rulesHelp2" tag="p" class="small">
|
||||||
|
<code>allow</code><code>disallow</code><code>regex</code>
|
||||||
|
</i18n>
|
||||||
|
|
||||||
<ul class="small">
|
<p class="small"><strong>{{ $t('settings.examples') }}</strong></p>
|
||||||
<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>
|
<ul class="small">
|
||||||
|
<li><code>disallow regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</li>
|
||||||
|
<li><code>disallow /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h2>{{ $t('settings.customStylesheet') }}</h2>
|
<textarea v-model.trim="rules"></textarea>
|
||||||
|
|
||||||
<textarea name="css"></textarea>
|
<h3>{{ $t('settings.customStylesheet') }}</h3>
|
||||||
|
|
||||||
<p>
|
<textarea name="css"></textarea>
|
||||||
<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>
|
</div>
|
||||||
<input type="submit" :value="$t('buttons.save')">
|
|
||||||
</p>
|
<div class="card-action">
|
||||||
|
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="flat delete" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||||
|
<input class="flat" type="submit" :value="$t('buttons.save')">
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div v-if="$store.state.show === 'deleteUser'" class="prompt">
|
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
|
||||||
<h3>Delete User</h3>
|
<div class="card-content">
|
||||||
<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>{{ $t('buttons.delete') }}</button>
|
|
||||||
<button class="cancel"
|
<div class="card-action">
|
||||||
|
<button class="cancel flat"
|
||||||
@click="closeHovers"
|
@click="closeHovers"
|
||||||
|
autofocus
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">
|
:title="$t('buttons.cancel')">
|
||||||
{{ $t('buttons.cancel') }}
|
{{ $t('buttons.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button class="flat"
|
||||||
|
@click="deleteUser">
|
||||||
|
{{ $t('buttons.delete') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,6 +97,7 @@ export default {
|
|||||||
allowEdit: false,
|
allowEdit: false,
|
||||||
allowCommands: false,
|
allowCommands: false,
|
||||||
allowPublish: false,
|
allowPublish: false,
|
||||||
|
lockPassword: false,
|
||||||
permissions: {},
|
permissions: {},
|
||||||
password: '',
|
password: '',
|
||||||
username: '',
|
username: '',
|
||||||
@@ -105,7 +110,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
passwordPlaceholder () {
|
passwordPlaceholder () {
|
||||||
if (this.$route.path === '/users/new') return ''
|
if (this.$route.path === '/settings/users/new') return ''
|
||||||
return this.$t('settings.avoidChanges')
|
return this.$t('settings.avoidChanges')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -120,6 +125,7 @@ export default {
|
|||||||
this.allowEdit = true
|
this.allowEdit = true
|
||||||
this.allowNew = true
|
this.allowNew = true
|
||||||
this.allowPublish = true
|
this.allowPublish = true
|
||||||
|
this.lockPassword = false
|
||||||
for (let key in this.permissions) {
|
for (let key in this.permissions) {
|
||||||
this.permissions[key] = true
|
this.permissions[key] = true
|
||||||
}
|
}
|
||||||
@@ -130,7 +136,7 @@ export default {
|
|||||||
fetchData () {
|
fetchData () {
|
||||||
let user = this.$route.params[0]
|
let user = this.$route.params[0]
|
||||||
|
|
||||||
if (this.$route.path === '/users/new') {
|
if (this.$route.path === '/settings/users/new') {
|
||||||
user = 'base'
|
user = 'base'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +147,7 @@ export default {
|
|||||||
this.allowNew = user.allowNew
|
this.allowNew = user.allowNew
|
||||||
this.allowEdit = user.allowEdit
|
this.allowEdit = user.allowEdit
|
||||||
this.allowPublish = user.allowPublish
|
this.allowPublish = user.allowPublish
|
||||||
|
this.lockPassword = user.lockPassword
|
||||||
this.filesystem = user.filesystem
|
this.filesystem = user.filesystem
|
||||||
this.username = user.username
|
this.username = user.username
|
||||||
this.commands = user.commands.join(' ')
|
this.commands = user.commands.join(' ')
|
||||||
@@ -166,7 +173,7 @@ export default {
|
|||||||
|
|
||||||
this.rules = this.rules.trim()
|
this.rules = this.rules.trim()
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.$router.push({ path: '/users/new' })
|
this.$router.push({ path: '/settings/users/new' })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
capitalize (name) {
|
capitalize (name) {
|
||||||
@@ -187,6 +194,7 @@ export default {
|
|||||||
this.allowPublish = false
|
this.allowPublish = false
|
||||||
this.permissins = {}
|
this.permissins = {}
|
||||||
this.allowCommands = false
|
this.allowCommands = false
|
||||||
|
this.lockPassword = false
|
||||||
this.password = ''
|
this.password = ''
|
||||||
this.username = ''
|
this.username = ''
|
||||||
this.filesystem = ''
|
this.filesystem = ''
|
||||||
@@ -202,22 +210,22 @@ export default {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
deleteUser(this.id).then(location => {
|
deleteUser(this.id).then(location => {
|
||||||
this.$router.push({ path: '/users' })
|
this.$router.push({ path: '/settings/users' })
|
||||||
this.$store.commit('showSuccess', this.$t('settings.userDeleted'))
|
this.$showSuccess(this.$t('settings.userDeleted'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$store.commit('showError', e)
|
this.$showError(e)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save (event) {
|
save (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
let user = this.parseForm()
|
let user = this.parseForm()
|
||||||
|
|
||||||
if (this.$route.path === '/users/new') {
|
if (this.$route.path === '/settings/users/new') {
|
||||||
newUser(user).then(location => {
|
newUser(user).then(location => {
|
||||||
this.$router.push({ path: location })
|
this.$router.push({ path: location })
|
||||||
this.$store.commit('showSuccess', this.$t('settings.userCreated'))
|
this.$showSuccess(this.$t('settings.userCreated'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$store.commit('showError', e)
|
this.$showError(e)
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -228,9 +236,9 @@ export default {
|
|||||||
this.$store.commit('setUser', user)
|
this.$store.commit('setUser', user)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit('showSuccess', this.$t('settings.userUpdated'))
|
this.$showSuccess(this.$t('settings.userUpdated'))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$store.commit('showError', e)
|
this.$showError(e)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
parseForm () {
|
parseForm () {
|
||||||
@@ -238,6 +246,7 @@ export default {
|
|||||||
ID: this.id,
|
ID: this.id,
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
|
lockPassword: this.lockPassword,
|
||||||
filesystem: this.filesystem,
|
filesystem: this.filesystem,
|
||||||
admin: this.admin,
|
admin: this.admin,
|
||||||
allowCommands: this.allowCommands,
|
allowCommands: this.allowCommands,
|
||||||
48
assets/src/views/settings/Users.vue
Normal file
48
assets/src/views/settings/Users.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('settings.users') }}</h2>
|
||||||
|
<router-link to="/settings/users/new"><button class="flat">{{ $t('buttons.new') }}</button></router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content full">
|
||||||
|
<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" :key="user.id">
|
||||||
|
<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 class="small">
|
||||||
|
<router-link :to="'/settings/users/' + user.ID"><i class="material-icons">mode_edit</i></router-link>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as api from '@/utils/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'users',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
users: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
api.getUsers().then(users => {
|
||||||
|
this.users = users
|
||||||
|
}).catch(error => {
|
||||||
|
this.$showError(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -48,6 +48,8 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
|||||||
scope := "."
|
scope := "."
|
||||||
database := ""
|
database := ""
|
||||||
noAuth := false
|
noAuth := false
|
||||||
|
reCaptchaKey := ""
|
||||||
|
reCaptchaSecret := ""
|
||||||
|
|
||||||
if plugin != "" {
|
if plugin != "" {
|
||||||
baseURL = "/admin"
|
baseURL = "/admin"
|
||||||
@@ -146,6 +148,27 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.CSS = string(css)
|
u.CSS = string(css)
|
||||||
|
case "view_mode":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
u.ViewMode = c.Val()
|
||||||
|
if u.ViewMode != "mosaic" && u.ViewMode != "list" {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
case "recaptcha_key":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
reCaptchaKey = c.Val()
|
||||||
|
case "recaptcha_secret":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
reCaptchaSecret = c.Val()
|
||||||
case "no_auth":
|
case "no_auth":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
noAuth = true
|
noAuth = true
|
||||||
@@ -204,10 +227,12 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := &filemanager.FileManager{
|
m := &filemanager.FileManager{
|
||||||
NoAuth: noAuth,
|
NoAuth: noAuth,
|
||||||
BaseURL: "",
|
BaseURL: "",
|
||||||
PrefixURL: "",
|
PrefixURL: "",
|
||||||
DefaultUser: u,
|
ReCaptchaKey: reCaptchaKey,
|
||||||
|
ReCaptchaSecret: reCaptchaSecret,
|
||||||
|
DefaultUser: u,
|
||||||
Store: &filemanager.Store{
|
Store: &filemanager.Store{
|
||||||
Config: bolt.ConfigStore{DB: db},
|
Config: bolt.ConfigStore{DB: db},
|
||||||
Users: bolt.UsersStore{DB: db},
|
Users: bolt.UsersStore{DB: db},
|
||||||
|
|||||||
@@ -24,22 +24,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addr string
|
addr string
|
||||||
config string
|
config string
|
||||||
database string
|
database string
|
||||||
scope string
|
scope string
|
||||||
commands string
|
commands string
|
||||||
logfile string
|
logfile string
|
||||||
staticg string
|
staticg string
|
||||||
locale string
|
locale string
|
||||||
port int
|
baseurl string
|
||||||
noAuth bool
|
prefixurl string
|
||||||
allowCommands bool
|
viewMode string
|
||||||
allowEdit bool
|
recaptchakey string
|
||||||
allowNew bool
|
recaptchasecret string
|
||||||
allowPublish bool
|
port int
|
||||||
showVer bool
|
noAuth bool
|
||||||
version = "master"
|
allowCommands bool
|
||||||
|
allowEdit bool
|
||||||
|
allowNew bool
|
||||||
|
allowPublish bool
|
||||||
|
showVer bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -49,7 +53,12 @@ func init() {
|
|||||||
flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
|
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(&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(&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.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
||||||
|
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
|
||||||
|
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
|
||||||
|
flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
|
||||||
|
flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
|
||||||
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(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
|
flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
|
||||||
@@ -74,6 +83,11 @@ func setupViper() {
|
|||||||
viper.SetDefault("StaticGen", "")
|
viper.SetDefault("StaticGen", "")
|
||||||
viper.SetDefault("Locale", "en")
|
viper.SetDefault("Locale", "en")
|
||||||
viper.SetDefault("NoAuth", false)
|
viper.SetDefault("NoAuth", false)
|
||||||
|
viper.SetDefault("BaseURL", "")
|
||||||
|
viper.SetDefault("PrefixURL", "")
|
||||||
|
viper.SetDefault("ViewMode", "mosaic")
|
||||||
|
viper.SetDefault("ReCaptchaKey", "")
|
||||||
|
viper.SetDefault("ReCaptchaSecret", "")
|
||||||
|
|
||||||
viper.BindPFlag("Port", flag.Lookup("port"))
|
viper.BindPFlag("Port", flag.Lookup("port"))
|
||||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||||
@@ -88,21 +102,18 @@ func setupViper() {
|
|||||||
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
||||||
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
|
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
|
||||||
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
||||||
|
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
||||||
|
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
||||||
|
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
|
||||||
|
viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
|
||||||
|
viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))
|
||||||
|
|
||||||
viper.SetConfigName("filemanager")
|
viper.SetConfigName("filemanager")
|
||||||
viper.AddConfigPath(".")
|
viper.AddConfigPath(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVersion() {
|
func printVersion() {
|
||||||
version = strings.TrimSpace(version)
|
fmt.Println("filemanager version", filemanager.Version)
|
||||||
|
|
||||||
if version == "" {
|
|
||||||
fmt.Println("filemanager is at an untracked version")
|
|
||||||
} else {
|
|
||||||
version = strings.TrimPrefix(version, "v")
|
|
||||||
fmt.Println("filemanager version", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,9 +187,11 @@ func handler() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fm := &filemanager.FileManager{
|
fm := &filemanager.FileManager{
|
||||||
NoAuth: viper.GetBool("NoAuth"),
|
NoAuth: viper.GetBool("NoAuth"),
|
||||||
BaseURL: "",
|
BaseURL: viper.GetString("BaseURL"),
|
||||||
PrefixURL: "",
|
PrefixURL: viper.GetString("PrefixURL"),
|
||||||
|
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
|
||||||
|
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
|
||||||
DefaultUser: &filemanager.User{
|
DefaultUser: &filemanager.User{
|
||||||
AllowCommands: viper.GetBool("AllowCommands"),
|
AllowCommands: viper.GetBool("AllowCommands"),
|
||||||
AllowEdit: viper.GetBool("AllowEdit"),
|
AllowEdit: viper.GetBool("AllowEdit"),
|
||||||
@@ -190,6 +203,7 @@ func handler() http.Handler {
|
|||||||
CSS: "",
|
CSS: "",
|
||||||
Scope: viper.GetString("Scope"),
|
Scope: viper.GetString("Scope"),
|
||||||
FileSystem: fileutils.Dir(viper.GetString("Scope")),
|
FileSystem: fileutils.Dir(viper.GetString("Scope")),
|
||||||
|
ViewMode: viper.GetString("ViewMode"),
|
||||||
},
|
},
|
||||||
Store: &filemanager.Store{
|
Store: &filemanager.Store{
|
||||||
Config: bolt.ConfigStore{DB: db},
|
Config: bolt.ConfigStore{DB: db},
|
||||||
|
|||||||
2
file.go
2
file.go
@@ -67,8 +67,6 @@ type Listing struct {
|
|||||||
Sort string `json:"sort"`
|
Sort string `json:"sort"`
|
||||||
// And which order.
|
// And which order.
|
||||||
Order string `json:"order"`
|
Order string `json:"order"`
|
||||||
// Displays in mosaic or list.
|
|
||||||
Display string `json:"display"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInfo gets the file information and, in case of error, returns the
|
// GetInfo gets the file information and, in case of error, returns the
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package filemanager
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -20,6 +21,9 @@ import (
|
|||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version is the current File Manager version.
|
||||||
|
const Version = "1.3.4"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrExist = errors.New("the resource already exists")
|
ErrExist = errors.New("the resource already exists")
|
||||||
ErrNotExist = errors.New("the resource does not exist")
|
ErrNotExist = errors.New("the resource does not exist")
|
||||||
@@ -62,6 +66,10 @@ type FileManager struct {
|
|||||||
// there will only exist one user, called "admin".
|
// there will only exist one user, called "admin".
|
||||||
NoAuth bool
|
NoAuth bool
|
||||||
|
|
||||||
|
// ReCaptcha Site key and secret.
|
||||||
|
ReCaptchaKey string
|
||||||
|
ReCaptchaSecret string
|
||||||
|
|
||||||
// StaticGen is the static websit generator handler.
|
// StaticGen is the static websit generator handler.
|
||||||
StaticGen StaticGen
|
StaticGen StaticGen
|
||||||
|
|
||||||
@@ -71,10 +79,28 @@ type FileManager struct {
|
|||||||
// A map of events to a slice of commands.
|
// A map of events to a slice of commands.
|
||||||
Commands map[string][]string
|
Commands map[string][]string
|
||||||
|
|
||||||
|
// Global stylesheet.
|
||||||
|
CSS string
|
||||||
|
|
||||||
// NewFS should build a new file system for a given path.
|
// NewFS should build a new file system for a given path.
|
||||||
NewFS FSBuilder
|
NewFS FSBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var commandEvents = []string{
|
||||||
|
"before_save",
|
||||||
|
"after_save",
|
||||||
|
"before_publish",
|
||||||
|
"after_publish",
|
||||||
|
"before_copy",
|
||||||
|
"after_copy",
|
||||||
|
"before_rename",
|
||||||
|
"after_rename",
|
||||||
|
"before_upload",
|
||||||
|
"after_upload",
|
||||||
|
"before_delete",
|
||||||
|
"after_delete",
|
||||||
|
}
|
||||||
|
|
||||||
// Command is a command function.
|
// Command is a command function.
|
||||||
type Command func(r *http.Request, m *FileManager, u *User) error
|
type Command func(r *http.Request, m *FileManager, u *User) error
|
||||||
|
|
||||||
@@ -108,16 +134,39 @@ func (m *FileManager) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the global CSS.
|
||||||
|
err = m.Store.Config.Get("css", &m.CSS)
|
||||||
|
if err != nil && err == ErrNotExist {
|
||||||
|
err = m.Store.Config.Save("css", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Tries to get the event commands from the database.
|
// Tries to get the event commands from the database.
|
||||||
// If they don't exist, initialize them.
|
// If they don't exist, initialize them.
|
||||||
err = m.Store.Config.Get("commands", &m.Commands)
|
err = m.Store.Config.Get("commands", &m.Commands)
|
||||||
if err != nil && err == ErrNotExist {
|
|
||||||
m.Commands = map[string][]string{
|
if err == nil {
|
||||||
"before_save": {},
|
// Add hypothetically new command handlers.
|
||||||
"after_save": {},
|
for _, command := range commandEvents {
|
||||||
"before_publish": {},
|
if _, ok := m.Commands[command]; ok {
|
||||||
"after_publish": {},
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Commands[command] = []string{}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err == ErrNotExist {
|
||||||
|
m.Commands = map[string][]string{}
|
||||||
|
|
||||||
|
// Initialize the command handlers.
|
||||||
|
for _, command := range commandEvents {
|
||||||
|
m.Commands[command] = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
err = m.Store.Config.Save("commands", m.Commands)
|
err = m.Store.Config.Save("commands", m.Commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +205,14 @@ func (m *FileManager) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove this after 1.5
|
||||||
|
for _, user := range users {
|
||||||
|
if user.ViewMode != "list" && user.ViewMode != "mosaic" {
|
||||||
|
user.ViewMode = "list"
|
||||||
|
m.Store.Users.Update(user, "ViewMode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m.DefaultUser.Username = ""
|
m.DefaultUser.Username = ""
|
||||||
m.DefaultUser.Password = ""
|
m.DefaultUser.Password = ""
|
||||||
|
|
||||||
@@ -232,7 +289,7 @@ func (m FileManager) ShareCleaner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Runner runs the commands for a certain event type.
|
// Runner runs the commands for a certain event type.
|
||||||
func (m FileManager) Runner(event string, path string) error {
|
func (m FileManager) Runner(event string, path string, destination string, user *User) error {
|
||||||
commands := []string{}
|
commands := []string{}
|
||||||
|
|
||||||
// Get the commands from the File Manager instance itself.
|
// Get the commands from the File Manager instance itself.
|
||||||
@@ -257,7 +314,15 @@ func (m FileManager) Runner(event string, path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(command, args...)
|
cmd := exec.Command(command, args...)
|
||||||
cmd.Env = append(os.Environ(), "file="+path)
|
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("ROOT=%s", string(user.Scope)))
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", event))
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
|
||||||
|
|
||||||
|
if destination != "" {
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", destination))
|
||||||
|
}
|
||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
@@ -286,6 +351,7 @@ var DefaultUser = User{
|
|||||||
AllowEdit: true,
|
AllowEdit: true,
|
||||||
AllowNew: true,
|
AllowNew: true,
|
||||||
AllowPublish: true,
|
AllowPublish: true,
|
||||||
|
LockPassword: false,
|
||||||
Commands: []string{},
|
Commands: []string{},
|
||||||
Rules: []*Rule{},
|
Rules: []*Rule{},
|
||||||
CSS: "",
|
CSS: "",
|
||||||
@@ -293,6 +359,7 @@ var DefaultUser = User{
|
|||||||
Locale: "en",
|
Locale: "en",
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
FileSystem: fileutils.Dir("."),
|
FileSystem: fileutils.Dir("."),
|
||||||
|
ViewMode: "mosaic",
|
||||||
}
|
}
|
||||||
|
|
||||||
// User contains the configuration for each user.
|
// User contains the configuration for each user.
|
||||||
@@ -325,6 +392,9 @@ type User struct {
|
|||||||
// Locale is the language of the user.
|
// Locale is the language of the user.
|
||||||
Locale string `json:"locale"`
|
Locale string `json:"locale"`
|
||||||
|
|
||||||
|
// Prevents the user to change its password.
|
||||||
|
LockPassword bool `json:"lockPassword"`
|
||||||
|
|
||||||
// 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
|
||||||
@@ -333,6 +403,9 @@ type User struct {
|
|||||||
|
|
||||||
// Commands is the list of commands the user can execute.
|
// Commands is the list of commands the user can execute.
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
|
|
||||||
|
// User view mode for files and folders.
|
||||||
|
ViewMode string `json:"viewMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allowed checks if the user has permission to access a directory/file.
|
// Allowed checks if the user has permission to access a directory/file.
|
||||||
|
|||||||
57
http/auth.go
57
http/auth.go
@@ -1,8 +1,11 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,6 +14,45 @@ import (
|
|||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type cred struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Recaptcha string `json:"recaptcha"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// recaptcha checks the recaptcha code.
|
||||||
|
func recaptcha(secret string, response string) (bool, error) {
|
||||||
|
api := "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
|
||||||
|
body := url.Values{}
|
||||||
|
body.Set("secret", secret)
|
||||||
|
body.Add("response", response)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Post(api, "application/x-www-form-urlencoded", bytes.NewBufferString(body.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
ChallengeTS time.Time `json:"challenge_ts"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
ErrorCodes interface{} `json:"error-codes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.Success, nil
|
||||||
|
}
|
||||||
|
|
||||||
// authHandler proccesses the authentication for the user.
|
// authHandler proccesses the authentication for the user.
|
||||||
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// NoAuth instances shouldn't call this method.
|
// NoAuth instances shouldn't call this method.
|
||||||
@@ -19,7 +61,7 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Receive the credentials from the request and unmarshal them.
|
// Receive the credentials from the request and unmarshal them.
|
||||||
var cred fm.User
|
var cred cred
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
@@ -29,6 +71,19 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
|
|||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If ReCaptcha is enabled, check the code.
|
||||||
|
if len(c.ReCaptchaSecret) > 0 {
|
||||||
|
ok, err := recaptcha(c.ReCaptchaSecret, cred.Recaptcha)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return http.StatusForbidden, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Checks if the user exists.
|
// Checks if the user exists.
|
||||||
u, err := c.Store.Users.GetByUsername(cred.Username, c.NewFS)
|
u, err := c.Store.Users.GetByUsername(cred.Username, c.NewFS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
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.File.Name)
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+c.File.Name+"\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
http.ServeFile(w, r, c.File.Path)
|
http.ServeFile(w, r, c.File.Path)
|
||||||
@@ -107,7 +107,7 @@ func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+name)
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
|
||||||
_, err = io.Copy(w, file)
|
_, err = io.Copy(w, file)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
59
http/http.go
59
http/http.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -53,11 +54,7 @@ func serve(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
|||||||
// Check if this request is made to the service worker. If so,
|
// Check if this request is made to the service worker. If so,
|
||||||
// pass it through a template to add the needed variables.
|
// pass it through a template to add the needed variables.
|
||||||
if r.URL.Path == "/sw.js" {
|
if r.URL.Path == "/sw.js" {
|
||||||
return renderFile(
|
return renderFile(c, w, "sw.js")
|
||||||
c, w,
|
|
||||||
c.Assets.MustString("sw.js"),
|
|
||||||
"application/javascript",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if this request is made to the static assets folder. If so, and
|
// Checks if this request is made to the static assets folder. If so, and
|
||||||
@@ -95,11 +92,7 @@ func serve(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
|||||||
w.Header().Set("x-content-type", "nosniff")
|
w.Header().Set("x-content-type", "nosniff")
|
||||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||||
|
|
||||||
return renderFile(
|
return renderFile(c, w, "index.html")
|
||||||
c, w,
|
|
||||||
c.Assets.MustString("index.html"),
|
|
||||||
"text/html",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// staticHandler handles the static assets path.
|
// staticHandler handles the static assets path.
|
||||||
@@ -109,11 +102,7 @@ func staticHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int,
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderFile(
|
return renderFile(c, w, "static/manifest.json")
|
||||||
c, w,
|
|
||||||
c.Assets.MustString("static/manifest.json"),
|
|
||||||
"application/json",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiHandler is the main entry point for the /api endpoint.
|
// apiHandler is the main entry point for the /api endpoint.
|
||||||
@@ -219,13 +208,31 @@ func splitURL(path string) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderFile renders a file using a template with some needed variables.
|
// renderFile renders a file using a template with some needed variables.
|
||||||
func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType string) (int, error) {
|
func renderFile(c *fm.Context, w http.ResponseWriter, file string) (int, error) {
|
||||||
tpl := template.Must(template.New("file").Parse(file))
|
tpl := template.Must(template.New("file").Parse(c.Assets.MustString(file)))
|
||||||
|
|
||||||
|
var contentType string
|
||||||
|
switch filepath.Ext(file) {
|
||||||
|
case ".html":
|
||||||
|
contentType = "text/html"
|
||||||
|
case ".js":
|
||||||
|
contentType = "application/javascript"
|
||||||
|
case ".json":
|
||||||
|
contentType = "application/json"
|
||||||
|
default:
|
||||||
|
contentType = "text"
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"BaseURL": c.RootURL(),
|
"BaseURL": c.RootURL(),
|
||||||
"NoAuth": c.NoAuth,
|
"NoAuth": c.NoAuth,
|
||||||
|
"Version": fm.Version,
|
||||||
|
"CSS": template.CSS(c.CSS),
|
||||||
|
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
|
||||||
|
"ReCaptchaKey": c.ReCaptchaKey,
|
||||||
|
"ReCaptchaSecret": c.ReCaptchaSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.StaticGen != nil {
|
if c.StaticGen != nil {
|
||||||
@@ -245,11 +252,8 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType s
|
|||||||
func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
s, err := c.Store.Share.Get(r.URL.Path)
|
s, err := c.Store.Share.Get(r.URL.Path)
|
||||||
if err == fm.ErrNotExist {
|
if err == fm.ErrNotExist {
|
||||||
return renderFile(
|
w.WriteHeader(http.StatusNotFound)
|
||||||
c, w,
|
return renderFile(c, w, "static/share/404.html")
|
||||||
c.Assets.MustString("static/share/404.html"),
|
|
||||||
"text/html",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -258,11 +262,8 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
|
|||||||
|
|
||||||
if s.Expires && s.ExpireDate.Before(time.Now()) {
|
if s.Expires && s.ExpireDate.Before(time.Now()) {
|
||||||
c.Store.Share.Delete(s.Hash)
|
c.Store.Share.Delete(s.Hash)
|
||||||
return renderFile(
|
w.WriteHeader(http.StatusNotFound)
|
||||||
c, w,
|
return renderFile(c, w, "static/share/404.html")
|
||||||
c.Assets.MustString("static/share/404.html"),
|
|
||||||
"text/html",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.URL.Path = s.Path
|
r.URL.Path = s.Path
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// Before save command handler.
|
// Before save command handler.
|
||||||
path := filepath.Join(c.User.Scope, r.URL.Path)
|
path := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
if err := c.Runner("before_save", path); err != nil {
|
if err := c.Runner("before_save", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
}
|
}
|
||||||
|
|
||||||
// After save command handler.
|
// After save command handler.
|
||||||
if err := c.Runner("after_save", path); err != nil {
|
if err := c.Runner("after_save", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +130,6 @@ func listingHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
listing.ApplySort()
|
listing.ApplySort()
|
||||||
listing.Display = displayMode(w, r, cookieScope)
|
|
||||||
|
|
||||||
return renderJSON(w, f)
|
return renderJSON(w, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,12 +139,22 @@ func resourceDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request
|
|||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire the before trigger.
|
||||||
|
if err := c.Runner("before_delete", r.URL.Path, "", c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the file or folder.
|
// Remove the file or folder.
|
||||||
err := c.User.FileSystem.RemoveAll(r.URL.Path)
|
err := c.User.FileSystem.RemoveAll(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire the after trigger.
|
||||||
|
if err := c.Runner("after_delete", r.URL.Path, "", c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +168,7 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Discard any invalid upload before returning to avoid connection
|
// Discard any invalid upload before returning to avoid connection
|
||||||
// reset fm.Error.
|
// reset error.
|
||||||
defer func() {
|
defer func() {
|
||||||
io.Copy(ioutil.Discard, r.Body)
|
io.Copy(ioutil.Discard, r.Body)
|
||||||
}()
|
}()
|
||||||
@@ -179,14 +187,19 @@ 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
|
// 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 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 {
|
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
|
||||||
return http.StatusConflict, errors.New("There is already a file on that path")
|
return http.StatusConflict, errors.New("There is already a file on that path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire the before trigger.
|
||||||
|
if err := c.Runner("before_upload", r.URL.Path, "", c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
// Create/Open the file.
|
// Create/Open the file.
|
||||||
f, err := c.User.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0776)
|
f, err := c.User.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0776)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -218,6 +231,12 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
// Writes the ETag Header.
|
// Writes the ETag Header.
|
||||||
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
||||||
w.Header().Set("ETag", etag)
|
w.Header().Set("ETag", etag)
|
||||||
|
|
||||||
|
// Fire the after trigger.
|
||||||
|
if err := c.Runner("after_upload", r.URL.Path, "", c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +275,7 @@ func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
path := filepath.Join(c.User.Scope, r.URL.Path)
|
path := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
|
|
||||||
// Before save command handler.
|
// Before save command handler.
|
||||||
if err := c.Runner("before_publish", path); err != nil {
|
if err := c.Runner("before_publish", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +285,7 @@ func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Executed the before publish command.
|
// Executed the before publish command.
|
||||||
if err := c.Runner("before_publish", path); err != nil {
|
if err := c.Runner("before_publish", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,40 +312,36 @@ func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if action == "copy" {
|
if action == "copy" {
|
||||||
|
// Fire the after trigger.
|
||||||
|
if err := c.Runner("before_copy", src, dst, c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the file.
|
||||||
err = c.User.FileSystem.Copy(src, dst)
|
err = c.User.FileSystem.Copy(src, dst)
|
||||||
|
|
||||||
|
// Fire the after trigger.
|
||||||
|
if err := c.Runner("after_copy", src, dst, c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Fire the after trigger.
|
||||||
|
if err := c.Runner("before_rename", src, dst, c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename the file.
|
||||||
err = c.User.FileSystem.Rename(src, dst)
|
err = c.User.FileSystem.Rename(src, dst)
|
||||||
|
|
||||||
|
// Fire the after trigger.
|
||||||
|
if err := c.Runner("after_rename", src, dst, c.User); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// displayMode obtains the display mode from the Cookie.
|
|
||||||
func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
|
|
||||||
var displayMode string
|
|
||||||
|
|
||||||
// Checks the cookie.
|
|
||||||
if displayCookie, err := r.Cookie("display"); err == nil {
|
|
||||||
displayMode = displayCookie.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's invalid, set it to mosaic, which is the default.
|
|
||||||
if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") {
|
|
||||||
displayMode = "mosaic"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cookie.
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "display",
|
|
||||||
Value: displayMode,
|
|
||||||
MaxAge: 31536000,
|
|
||||||
Path: scope,
|
|
||||||
Secure: r.TLS != nil,
|
|
||||||
})
|
|
||||||
|
|
||||||
return displayMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
||||||
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
||||||
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
|
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
type modifySettingsRequest struct {
|
type modifySettingsRequest struct {
|
||||||
*modifyRequest
|
*modifyRequest
|
||||||
Data struct {
|
Data struct {
|
||||||
|
CSS string `json:"css"`
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
StaticGen map[string]interface{} `json:"staticGen"`
|
StaticGen map[string]interface{} `json:"staticGen"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
@@ -61,6 +62,7 @@ func settingsHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
}
|
}
|
||||||
|
|
||||||
type settingsGetRequest struct {
|
type settingsGetRequest struct {
|
||||||
|
CSS string `json:"css"`
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
StaticGen []option `json:"staticGen"`
|
StaticGen []option `json:"staticGen"`
|
||||||
}
|
}
|
||||||
@@ -73,6 +75,7 @@ func settingsGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
|
|||||||
result := &settingsGetRequest{
|
result := &settingsGetRequest{
|
||||||
Commands: c.Commands,
|
Commands: c.Commands,
|
||||||
StaticGen: []option{},
|
StaticGen: []option{},
|
||||||
|
CSS: c.CSS,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.StaticGen != nil {
|
if c.StaticGen != nil {
|
||||||
@@ -114,6 +117,16 @@ func settingsPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
|
|||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the global CSS.
|
||||||
|
if mod.Which == "css" {
|
||||||
|
if err := c.Store.Config.Save("css", mod.Data.CSS); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.CSS = mod.Data.CSS
|
||||||
|
return http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Update the static generator options.
|
// Update the static generator options.
|
||||||
if mod.Which == "staticGen" {
|
if mod.Which == "staticGen" {
|
||||||
err = mapstructure.Decode(mod.Data.StaticGen, c.StaticGen)
|
err = mapstructure.Decode(mod.Data.StaticGen, c.StaticGen)
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(s) == 0 {
|
||||||
|
return http.StatusNotFound, nil
|
||||||
|
}
|
||||||
|
|
||||||
return renderJSON(w, s)
|
return renderJSON(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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", "/settings/users/"+strconv.Itoa(u.ID))
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@@ -268,12 +268,20 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're updating the default user. Only for NoAuth
|
||||||
|
// implementations. Used to change the viewMode.
|
||||||
|
if id == 0 && c.NoAuth {
|
||||||
|
c.DefaultUser.ViewMode = u.ViewMode
|
||||||
|
return http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Updates the CSS and locale.
|
// Updates the CSS and locale.
|
||||||
if which == "partial" {
|
if which == "partial" {
|
||||||
c.User.CSS = u.CSS
|
c.User.CSS = u.CSS
|
||||||
c.User.Locale = u.Locale
|
c.User.Locale = u.Locale
|
||||||
|
c.User.ViewMode = u.ViewMode
|
||||||
|
|
||||||
err = c.Store.Users.Update(c.User, "CSS", "Locale")
|
err = c.Store.Users.Update(c.User, "CSS", "Locale", "ViewMode")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
@@ -287,6 +295,10 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
|||||||
return http.StatusBadRequest, fm.ErrEmptyPassword
|
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)
|
c.User.Password, err = fm.HashPassword(u.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
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",
|
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-7.0.0.tgz",
|
||||||
"integrity": "sha1-q/sd2CRwZ04DIrU86xqvQSk45L8="
|
"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": {
|
"npm-run-path": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"filesize": "^3.5.10",
|
"filesize": "^3.5.10",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
"normalize.css": "^7.0.0",
|
"normalize.css": "^7.0.0",
|
||||||
|
"noty": "^3.1.2",
|
||||||
"vue": "^2.3.3",
|
"vue": "^2.3.3",
|
||||||
"vue-i18n": "^7.1.0",
|
"vue-i18n": "^7.1.0",
|
||||||
"vue-router": "^2.7.0",
|
"vue-router": "^2.7.0",
|
||||||
|
|||||||
24
publish.sh
Normal file
24
publish.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Building assets"
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
echo "Updating version number to $1..."
|
||||||
|
sed -i "s|(untracked)|$1|g" filemanager.go
|
||||||
|
|
||||||
|
echo "Commiting..."
|
||||||
|
git add -A
|
||||||
|
git commit -m "Version $1"
|
||||||
|
git push
|
||||||
|
|
||||||
|
echo "Creating the tag..."
|
||||||
|
git tag "v$1"
|
||||||
|
git push --tags
|
||||||
|
|
||||||
|
echo "Commiting untracked version notice..."
|
||||||
|
sed -i "s|$1|(untracked)|g" filemanager.go
|
||||||
|
git add -A
|
||||||
|
git commit -m "[ci skip] auto: setting untracked version"
|
||||||
|
git push
|
||||||
|
|
||||||
|
echo "Done!"
|
||||||
@@ -1 +1 @@
|
|||||||
7ef84c6326cbac0b4baba0591c4669ed50c8bfec
|
be5b409fc6ffa5e4c331a99a624617b653373bee
|
||||||
Reference in New Issue
Block a user