Compare commits

...

26 Commits

Author SHA1 Message Date
Henrique Dias
1077d5cd6b Version 1.3.7
Former-commit-id: c76a13cb8d0bf0a360c63c18cbfac378f8888425 [formerly e58788aa3de9e8fb54894a72bdd567c3846bf943] [formerly 77cd4f2feb8ef317cfeeff84d85ffb7eff4d899f [formerly 3cddb26572]]
Former-commit-id: af62db2d663287f662990539be428ae84f1974cf [formerly eaae8679ea0b9daeeba5cd29dd981d60d66e6c1e]
Former-commit-id: 886b61f0af26b85625cf7092ce5e7d4c5e44ed59
2017-10-29 13:33:31 +00:00
Henrique Dias
57cc174b3d [ci skip] auto: setting untracked version
Former-commit-id: ed15673452af4cd3ccf1a1f316efd6a733e6bd43 [formerly 0a9637c464bbae4c5c351f7e6f65dd24197a9968] [formerly e7388628831533d4476ba69c8d0cfac3ae18be14 [formerly 08bfeea103]]
Former-commit-id: 412e1b3bfcdef0037292b7ab3f5a5eeb4e9523f3 [formerly 2ca352a5af95eedadc91ea207833bdb670f1481a]
Former-commit-id: b359f15e105d55a9761aa9bf43c331bad113d561
2017-10-29 13:32:10 +00:00
Henrique Dias
cf61baa273 Version 1.3.4
Former-commit-id: 6d70d81a1967f171a41cecbdad8664fc65d1111a [formerly e273e78de0030dcb2eec730a00f248f1e8dbd9f7] [formerly 37b0ede2385e153153a854112ffab1065bcdd44d [formerly f89b86939e]]
Former-commit-id: 84fc2f9742c2e0011254ee762faf6f087986c6fa [formerly f22c175e96173bf25aa08067533937041152fdb3]
Former-commit-id: 32981c2a1211608d2d5c059264aa5eb434099400
2017-10-29 13:32:02 +00:00
Henrique Dias
2d5cd2d1d3 Fix Travis
Former-commit-id: 435a3bebf2ac1bbae2f1ed94d596956227141358 [formerly 46e69c9783cb07a9ca6b7bd428a1ab54cff2005b] [formerly 36ed5ea293a4d39d161bffed5b18a1d7f1a0d5c1 [formerly 3434090a7c]]
Former-commit-id: d5bc8f96b631af8d440438c1a089e710ae09a47a [formerly f447c0c7031af05129bf7c45b6c87168a365738d]
Former-commit-id: 627ac93b54b236e20d552486d125cef813eaf9e5
2017-10-29 13:12:54 +00:00
Henrique Dias
9472aad877 Fix #262 #266
Former-commit-id: 0acca700ed0a2ced8bf6b5b019aa6ff4774b8ea8 [formerly 0ff934dd107e56f4b7349f1363dfb1b6cbbc7916] [formerly f8b60e60087d754b46ca4dad49a151bec299febd [formerly ae1702c4e2]]
Former-commit-id: 824abe0c6cb5d32c061e8efd4a7f1d3354b3dbfe [formerly 655809d5c670c9de352c473de1fe08303a4c5506]
Former-commit-id: 6ca6f383602241b8a50c13cce5b066cd5f2cf990
2017-10-29 13:07:48 +00:00
Henrique Dias
dd0f3ef144 [ci skip] auto: setting untracked version
Former-commit-id: 7abc56816c4aee81b91bccce14251ed71b02ac0e [formerly 5d1acb37bf487778cb740299a0f25846f49f0ad4] [formerly b97420d631839e9a7a74961403113986d493638f [formerly 26e1e44d60]]
Former-commit-id: c196dfa47127e87efcec64c4cda2e7013e5cdc4d [formerly 5647ffd57b1e049a3e11f6ec37fd2344abfc591a]
Former-commit-id: 3cc55fc8d38f553f0405972dd39da2b0bbb52740
2017-10-15 08:28:41 +01:00
Henrique Dias
44e492160b Version 1.3.6
Former-commit-id: 28c72f8ee9f7cf548dd5f2c2cc564b31ba13ada8 [formerly cd087b92826cb418d4d17ac26a8cfeb408a54919] [formerly bf241b663bdcfc00737f5bb429ef4a669c1a6c2b [formerly 3e9c3ed912]]
Former-commit-id: 2b74d089de06c4fa5d4933e0c11ff8b1afcd2f46 [formerly 9ac185e1ea76c1beec895f6c51109fd137547e60]
Former-commit-id: a27b4e08dc726769b4731abb5865d2e908387774
2017-10-15 08:28:28 +01:00
Henrique Dias
0137b03887 Fix Portuguese language reference and DRY
Former-commit-id: 3954a92011b3c1699236b32373688ce80f720179 [formerly 67bca685bac4e893f836b21f85f4369444f3e708] [formerly 9817c40128b6660be30ea48b5317d07d21b84ca0 [formerly 77df2034f5]]
Former-commit-id: 2681c1f068e8094f0f9593a4c21fc6f84d8a6353 [formerly 26986c2af1193a31e3abec64b829cb29aa97011c]
Former-commit-id: c2813afc8de2e22ff9f3de0f55fbb26b3b954619
2017-10-15 08:26:22 +01:00
Equim
92c9b134c3 i18n: auto detect locale from browser (#253)
* i18n: detect locale from browser

* fix regex for locale matching

* remove debug code


Former-commit-id: 17e550af54ff213d5e2b60f83b374cd962052b5b [formerly 62fb089a7a45092b3818135dd68fac218067ef67] [formerly 733c463d2332307dafd40da5e77c6c9558239283 [formerly 4b84492a11]]
Former-commit-id: 2e117c9e060ac5cd9f80a0de2a4582eef74df6b8 [formerly 5fb6fc086bb2ebeb49a578042240a26a7879a4b8]
Former-commit-id: 7692a4fbb2889acbd7c6ee09ccd01a234998616a
2017-10-15 08:12:40 +01:00
Geno
1737702c7c typo in logging (#255)
Former-commit-id: fd0b7d6887e14b77ab97b323f4e8d1bb51ecda23 [formerly fb4fab794ca27e8f77e0dec71483cec51e4a8fce] [formerly df9da803a4301a8b1e5ed93f1f3fd121b8a3bf49 [formerly b93aed98b1]]
Former-commit-id: d17f1d4be63659781b4254b9ec36737d2f794008 [formerly 27cc8277db82e0ae123d2b1ffe95bed297c6095d]
Former-commit-id: 8137dd86f6f3caef4636416f6370bd686773df3a
2017-10-15 08:06:53 +01:00
Equim
ece52ecf7c Don't expose error (#254)
* share: remove share link when it has been moved

* http: don't expose error


Former-commit-id: c7f1d28117c770006132c75e5950d73aa9d87a12 [formerly f29a0260622644a79ff401263ba4efb143dea23a] [formerly fdd741679f09c72a121076e0a62a0d2a6eafefe4 [formerly 538b99ee77]]
Former-commit-id: 5ec9b62254b0cbc233673e7e196a5a4ece53a3f4 [formerly d26d45418267d11a1a211ba90a8b68b5b9fad714]
Former-commit-id: 57333d74cbac7088c6c527a4fe757af427dedea8
2017-10-14 10:07:01 +01:00
Henrique Dias
a7d6a72718 Build Assets
Former-commit-id: aa40a922e2dbd104bed161fd6564343c64f2fb34 [formerly 16a73ea595678b00ab9690338015746a8f5e5f02] [formerly cf4ef0a6fe12d211a1e905047a3a2cdba12e9c84 [formerly 7f7d536c5e]]
Former-commit-id: cb509a9c9961b4c8d2c6f8f67b37f30b2732eef8 [formerly f7465805945f0c08312c31bb067e3d9546d883bd]
Former-commit-id: cab59e68c27cd7c180a8964889c759efbbff7616
2017-10-09 08:28:49 +01:00
Henrique Dias
3fa9286238 ViewMode constants; default view modes
Former-commit-id: d51ad4b2671c76c3a493daf93fd4fb94a76826ed [formerly 576cfa8ebe9c66c9dbc1c8f79ab8ba7fecbe8845] [formerly 0969e87e3c070763d6e58f1d09f815b772814e4d [formerly 8633e9677b]]
Former-commit-id: 4dad4cf6bb3858122cd4d4c6a574e691788ef9ae [formerly 0b356f24a005c8886bd2165884e363f277456d8f]
Former-commit-id: cadb7589dad74e9bdb47bf3664f98ac672940fe5
2017-10-09 08:26:05 +01:00
Henrique Dias
ad5ff4cfe0 Close #248
Former-commit-id: af79c3515a4a6a6d5b72da1193b888b7cd03b286 [formerly bb0e1f8e966e238feacf3013eccf9b5e711f7061] [formerly 0f38f0df6f964f88310fde0e59f6b3a79b9c103c [formerly 90c2de44c3]]
Former-commit-id: ac03a781bce6530b0512b014c38e8d8c77d8ec01 [formerly 5469e2322432fd1991229f909e69d70966fe8f9e]
Former-commit-id: d17517418e68671237ad845df93b62840038d8d7
2017-10-09 08:14:17 +01:00
Henrique Dias
9aee1ebd2a [ci skip] auto: setting untracked version
Former-commit-id: 89e071320621710d9f24bf4dde8827f159c64d31 [formerly 6d44fe3f3e56b82762bacce64c1682262d4d1a90] [formerly 8e0f92b3205067d4644bf2febdc160d84696a37a [formerly 769bdad12a]]
Former-commit-id: 9e00c24fdca0e1ebe71aceb84624ea0b9fb85afa [formerly a04f2e37805000e19c7dcdb7daafe2396cb8269e]
Former-commit-id: 827a695fce5d7731e58052dbbf4f5bd0e3d6f5c7
2017-10-08 23:48:24 +01:00
Henrique Dias
f8ed1b41d6 Version 1.3.5
Former-commit-id: e064282645a50d6eeeb293577da260c586d4bb45 [formerly 2a9ac206ab79a8f115ed25a3afb595663f18fcb4] [formerly 2ea943c96653266a9533d9e95776955821706ea4 [formerly dd59e3c62b]]
Former-commit-id: 4ec9b9738ee48943830b9045ae40e7c305010bf3 [formerly 7748b70d5109e788ad146b466b3430a822037570]
Former-commit-id: 0981b89cb1d7bc0da43d47ff8900eb1c67301982
2017-10-08 23:47:56 +01:00
Equim
bb9b0dfd2b i18n: Fix typo (#250)
Former-commit-id: c27f105374233923e7691327f7e6f6b9a52077ce [formerly c34be33394c74229a1029edf21d9c83d5c9dd2a9] [formerly 3f5bd5a8457310d034b7fe761beb915caf35ae65 [formerly f0609757f7]]
Former-commit-id: 82d69494e5c421a245ceff0df033529ef488e419 [formerly 910438160faa1e853c91252845443514a066b3f9]
Former-commit-id: 01874c63eb13dc39471c3c2cd06a9b2e99394f46
2017-10-08 23:42:51 +01:00
Henrique Dias
cc2ce884fc [ci skip] auto: setting untracked version
Former-commit-id: d68d28c1e0000f0308b1e84a912f00b8a91f7fbf [formerly 15e0131f25aa82b8bb340e2d58d09c35a4a91754] [formerly 282b0c8d07c82de35fb87927c9da29182efaceff [formerly 9d27c38097]]
Former-commit-id: 9cc06bb0c230f2acac72fc6b4d00e4d8446a5f88 [formerly 5634a96f7b01d6f08a6a46f24dab7a3f03668fd6]
Former-commit-id: c9629add111d645e386461016771ebd1b14b98b5
2017-09-11 09:49:54 +01:00
Henrique Dias
e7e7679002 Version 1.3.4
Former-commit-id: f7727c37ed227e062a34de8b67a5bdeb95caab27 [formerly 9202314bd4a0a0288e3b95c179aaeaaf38997f7f] [formerly c57ba427c5e85f4aff34ccc5476d2c374c93a6d4 [formerly d41715c0c2]]
Former-commit-id: 7aa4c2ee81fa7dd451fef64198335f7a6ee78173 [formerly 4d1553375b1a3c86e9f8de45ef658ff8fe64aba4]
Former-commit-id: 85340361dc98b2ce775d1c41291ff3af165f99ee
2017-09-11 09:49:42 +01:00
Henrique Dias
9a829fd594 fix #241
Former-commit-id: 8578bf0b790ea4b8b5c5da4876fbccd2ead42d3c [formerly b67d35502fb0c9a3c57226b812dd2b869c5fcae1] [formerly 506eb279c974a86b232be57f87a11ec283b3b742 [formerly f658394dd7]]
Former-commit-id: e9565ce6a4ef229943f132fb6e05c5ec853447ed [formerly d0b2c24f6df2a74c403ded829cfd0746659e7d5f]
Former-commit-id: 2f4afe92081915821dd5b2fe745faf1492bdcabf
2017-09-11 09:46:17 +01:00
Henrique Dias
624d61930c Add ReCaptcha to main
Former-commit-id: 06bc7079f6d939e5531a3d9600052f979adac86d [formerly e6d8fa4418ccfa8f0163530647099432a936d4ee] [formerly dbd0cfc3770972afdf7aae8121d3af50681d55eb [formerly 879ad7b518]]
Former-commit-id: e6706456ba300c501ae66664596b5709e45d87df [formerly 98238eb61781a545ee4bac512a4f02257f4cf165]
Former-commit-id: 30bfb2b201ecbbd1ac2cf58cbadbe82daea793cb
2017-09-11 09:23:59 +01:00
Henrique Dias
ee30e7711f implement recaptcha on login
Former-commit-id: d7495b6fff4a99a8d155a3be87b15535a74a1305 [formerly 5b3a544447cca0d1cdcb6c87ca94f450a5493506] [formerly b4de1a4f5d4dd295c98366ede2b87bf2cb7918f9 [formerly 002f8066c7]]
Former-commit-id: c0e5d38111a99f8e3e71fb5db86e19b7ba44ec48 [formerly 1b5e454263ba64ced95c6d4b51f5f32e66f74758]
Former-commit-id: cfb17a53fc86d0071fba91503502444f5f10a0c7
2017-09-11 09:00:59 +01:00
Henrique Dias
6e5116aa27 [ci skip] auto: setting untracked version
Former-commit-id: 4f0cecd21f2f1fec680773c6242d6ab9687384f3 [formerly 1dec18820a98ad1ffc39eddff12167b4d1564254] [formerly ffdde8ede3a4d934d492224a046b654518cffbe9 [formerly a61329843c]]
Former-commit-id: 865c60a764807ea8d6781ac4bc95a4c8ddd9ef56 [formerly c9799373cf5e9637c78faffb94117c355a3a4f8c]
Former-commit-id: 070a498ca4c9d6da545c9b10d60e0f84e7e56889
2017-09-10 10:14:35 +01:00
Henrique Dias
34acffbb7b Version 1.3.3
Former-commit-id: d3126ad3137ddb4270199227c7ac4a0aa248b117 [formerly d8de0edac44991fb25fc3d58bc5fbf34ca51e14c] [formerly 65f8bd6a9c9a15f2182f7d9d0d0317df8a7a636c [formerly 53dedb7f5e]]
Former-commit-id: 473f94e0b181bfe06eaf73648775af5e89b03032 [formerly 9efded6125dc4ef0384f441933139847513fde51]
Former-commit-id: 8ada7c0571decba2abb0e721650401f040a6fb86
2017-09-10 10:14:22 +01:00
Henrique Dias
eb6f26c191 Add view mode on users created on previous versions
Former-commit-id: 6c3e320b2004beb8172e966e1a7f1885e33ed20b [formerly 829930444312dcfa1e144b3631c56632f6094d01] [formerly 68ae2ee9df5b3908fb30a07094f7d464818a0d52 [formerly b651d03ea9]]
Former-commit-id: 3af8ad9075641ca34504e33a2eb363eff7c6c63d [formerly e2873038181f9cc6f8d1c465aa5544cf98eb735d]
Former-commit-id: 584b5c3452545d2a6361ade02e093fe5596a52c3
2017-09-10 10:13:10 +01:00
Henrique Dias
06f3e9744a [ci skip] auto: setting untracked version
Former-commit-id: c7af338241ab6cac5e5e6f5683a8394e30a1ff6b [formerly a28f7d51e97810acd69616c22eb578f2d65e8528] [formerly 6b372592bf387d73b10e42a29ab75a5b99647d65 [formerly 750862b17d]]
Former-commit-id: a6a9cedfc2f7507080b39c665d97e8b586a746de [formerly ceeb7a7fc060245d21d169eb22b6b099fc4c384d]
Former-commit-id: 5dcf4b4c5a1caf3b482baf7f0f8aecb1b69a9263
2017-09-07 18:28:22 +01:00
27 changed files with 317 additions and 91 deletions

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ node_modules/
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.idea
.vscode

View File

@@ -15,7 +15,7 @@ install:
- go get github.com/tsenart/deadcode - go get github.com/tsenart/deadcode
script: script:
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --exclude="rice-box.go" --tests ./... - gometalinter --disable-all -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --exclude="rice-box.go" --tests ./...
- go test ./... -timeout 30s - go test ./... -timeout 30s
after_success: after_success:

View File

@@ -8,6 +8,7 @@
<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="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">
@@ -27,6 +28,10 @@
<script>CSS = "{{ .CSS }}"</script> <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)$/)) { %>

View File

@@ -1,22 +1,48 @@
<template> <template>
<router-view @update:css="updateCSS" @clean:css="cleanCSS"></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',
computed: mapState(['recaptcha']),
data () {
return {
loaded: false
}
},
mounted () { mounted () {
// Remove loading animation. if (this.recaptcha.length === 0) {
let loading = document.getElementById('loading') this.unload()
loading.classList.add('done') return
}
setTimeout(function () { let check = () => {
loading.parentNode.removeChild(loading) if (typeof window.grecaptcha === 'undefined') {
}, 200) setTimeout(check, 100)
return
}
this.updateCSS() this.unload()
}
check()
}, },
methods: { 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) { updateCSS (global = false) {
let css = this.$store.state.css let css = this.$store.state.css

View File

@@ -196,6 +196,10 @@ export default {
}) })
}, },
paste (event) { paste (event) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
event.preventDefault() event.preventDefault()
let items = [] let items = []

View File

@@ -16,6 +16,7 @@
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat" <button class="flat"
@click="copy" @click="copy"
:disabled="$route.path === dest"
:aria-label="$t('buttons.copy')" :aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button> :title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
</div> </div>

View File

@@ -15,6 +15,7 @@
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button> :title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat" <button class="flat"
@click="move" @click="move"
:disabled="$route.path === dest"
:aria-label="$t('buttons.move')" :aria-label="$t('buttons.move')"
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button> :title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
</div> </div>

View File

@@ -112,6 +112,11 @@ button.flat.cancel {
color: #ccc; color: #ccc;
} }
button.flat[disabled] {
color: #ccc;
cursor: not-allowed;
}
.mobile-only { .mobile-only {
display: none !important; display: none !important;
} }

View File

@@ -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%;

View File

@@ -9,8 +9,39 @@ import zhTW from './zh-tw.yaml'
Vue.use(VueI18n) Vue.use(VueI18n)
export function detectLocale () {
let locale = (navigator.language || navigator.browserLangugae).toLowerCase()
switch (true) {
case /^en.*/i.test(locale):
locale = 'en'
break
case /^fr.*/i.test(locale):
locale = 'fr'
break
case /^pt.*/i.test(locale):
locale = 'pt'
break
case /^ja.*/i.test(locale):
locale = 'ja'
break
case /^zh-CN/i.test(locale):
locale = 'zh-cn'
break
case /^zh-TW/i.test(locale):
locale = 'zh-tw'
break
case /^zh.*/i.test(locale):
locale = 'zh-cn'
break
default:
locale = 'en'
}
return locale
}
const i18n = new VueI18n({ const i18n = new VueI18n({
locale: 'en', locale: detectLocale(),
fallbackLocale: 'en', fallbackLocale: 'en',
messages: { messages: {
'en': en, 'en': en,

View File

@@ -124,7 +124,7 @@ settings:
examples: examples:
globalSettings: グローバル設定 globalSettings: グローバル設定
language: 言語 language: 言語
lockPassowrd: 新しいパスワードを変更に禁止 lockPassword: 新しいパスワードを変更に禁止
newPassword: 新しいパスワード newPassword: 新しいパスワード
newPasswordConfirm: 新しいパスワードを確認します newPasswordConfirm: 新しいパスワードを確認します
newUser: 新しいユーザー newUser: 新しいユーザー

View File

@@ -123,7 +123,7 @@ settings:
examples: 例子 examples: 例子
globalSettings: 全局设置 globalSettings: 全局设置
language: 语言 language: 语言
lockPassowrd: 禁止用户修改密码 lockPassword: 禁止用户修改密码
newPassword: 您的新密码 newPassword: 您的新密码
newPasswordConfirm: 重输一遍新密码 newPasswordConfirm: 重输一遍新密码
newUser: 新建用户 newUser: 新建用户

View File

@@ -31,7 +31,7 @@ Vue.prototype.$showError = function (error) {
type: 'error', type: 'error',
timeout: null, timeout: null,
buttons: [ buttons: [
Noty.button(i18n.t('buttons.reportIssue'), 'cancel', function () { Noty.button(i18n.t('buttons.reportIssue'), '', function () {
window.open('https://github.com/hacdias/filemanager/issues/new') window.open('https://github.com/hacdias/filemanager/issues/new')
}), }),
Noty.button(i18n.t('buttons.close'), '', function () { Noty.button(i18n.t('buttons.close'), '', function () {

View File

@@ -11,7 +11,7 @@ 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'
import auth from '@/utils/auth.js' import auth from '@/utils/auth'
import store from '@/store' import store from '@/store'
Vue.use(Router) Vue.use(Router)
@@ -51,14 +51,13 @@ const router = new Router({
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
component: Settings, component: Settings,
redirect: {
path: '/settings/profile'
},
meta: {
disableOnNoAuth: true
},
children: [ children: [
{
path: '/settings',
name: 'Settings',
redirect: {
path: '/settings/profile'
}
},
{ {
path: '/settings/profile', path: '/settings/profile',
name: 'Profile Settings', name: 'Profile Settings',
@@ -131,16 +130,17 @@ router.beforeEach((to, from, next) => {
auth.loggedIn() auth.loggedIn()
.then(() => { .then(() => {
if (to.matched.some(record => record.meta.requiresAdmin)) { if (to.matched.some(record => record.meta.requiresAdmin)) {
if (store.state.user.admin) { if (!store.state.user.admin) {
next() next({ path: '/403' })
return return
} }
}
next({ if (to.matched.some(record => record.meta.disableOnNoAuth)) {
path: '/403' if (store.state.noAuth) {
}) next({ path: '/403' })
return
return }
} }
next() next()

View File

@@ -17,6 +17,7 @@ const state = {
window.CSS = null window.CSS = null
return css 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'),

View File

@@ -1,4 +1,4 @@
import i18n from '@/i18n' import * as i18n from '@/i18n'
import moment from 'moment' import moment from 'moment'
const mutations = { const mutations = {
@@ -27,8 +27,14 @@ const mutations = {
setLoading: (state, value) => { state.loading = value }, setLoading: (state, value) => { state.loading = value },
setReload: (state, value) => { state.reload = value }, setReload: (state, value) => { state.reload = value },
setUser: (state, value) => { setUser: (state, value) => {
moment.locale(value.locale) let locale = value.locale
i18n.locale = value.locale
if (locale === '') {
locale = i18n.detectLocale()
}
moment.locale(locale)
i18n.default.locale = locale
state.user = value state.user = value
}, },
setCSS: (state, value) => (state.css = value), setCSS: (state, value) => (state.css = value),

View File

@@ -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)

View File

@@ -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 })
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="dashboard"> <div class="dashboard">
<form class="card" @submit.prevent="saveStaticGen"> <form class="card" v-if="staticGen.length" @submit.prevent="saveStaticGen">
<div class="card-title"> <div class="card-title">
<h2>{{ capitalize($store.state.staticGen) }}</h2> <h2>{{ capitalize($store.state.staticGen) }}</h2>
</div> </div>
@@ -80,7 +80,6 @@ export default {
created () { created () {
getSettings() getSettings()
.then(settings => { .then(settings => {
console.log(settings)
if (this.$store.state.staticGen.length > 0) { if (this.$store.state.staticGen.length > 0) {
this.parseStaticGen(settings.staticGen) this.parseStaticGen(settings.staticGen)
} }

View File

@@ -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"
@@ -152,9 +154,21 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
} }
u.ViewMode = c.Val() u.ViewMode = c.Val()
if u.ViewMode != "mosaic" && u.ViewMode != "list" { if u.ViewMode != filemanager.MosaicViewMode && u.ViewMode != filemanager.ListViewMode {
return nil, c.ArgErr() 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
@@ -193,7 +207,7 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
sha := hex.EncodeToString(hasher.Sum(nil)) sha := hex.EncodeToString(hasher.Sum(nil))
database = filepath.Join(path, sha+".db") database = filepath.Join(path, sha+".db")
fmt.Println("[WARNING] A database is going to be created for your File Manager instace at " + database + fmt.Println("[WARNING] A database is going to be created for your File Manager instance at " + database +
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n") ". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
} }
@@ -213,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},

View File

@@ -24,24 +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
baseurl string baseurl string
prefixurl string prefixurl string
viewMode string viewMode string
port int recaptchakey string
noAuth bool recaptchasecret string
allowCommands bool port int
allowEdit bool noAuth bool
allowNew bool allowCommands bool
allowPublish bool allowEdit bool
showVer bool allowNew bool
allowPublish bool
showVer bool
) )
func init() { func init() {
@@ -55,12 +57,14 @@ func init() {
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(&prefixurl, "prefixurl", "", "Prefix URL")
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users") 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")
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users") flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication") flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
flag.StringVar(&locale, "locale", "en", "Default locale for new users") flag.StringVar(&locale, "locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable") flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
flag.BoolVarP(&showVer, "version", "v", false, "Show version") flag.BoolVarP(&showVer, "version", "v", false, "Show version")
} }
@@ -77,11 +81,13 @@ func setupViper() {
viper.SetDefault("AllowNew", true) viper.SetDefault("AllowNew", true)
viper.SetDefault("AllowPublish", true) viper.SetDefault("AllowPublish", true)
viper.SetDefault("StaticGen", "") viper.SetDefault("StaticGen", "")
viper.SetDefault("Locale", "en") viper.SetDefault("Locale", "")
viper.SetDefault("NoAuth", false) viper.SetDefault("NoAuth", false)
viper.SetDefault("BaseURL", "") viper.SetDefault("BaseURL", "")
viper.SetDefault("PrefixURL", "") viper.SetDefault("PrefixURL", "")
viper.SetDefault("ViewMode", "mosaic") viper.SetDefault("ViewMode", filemanager.MosaicViewMode)
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"))
@@ -99,6 +105,8 @@ func setupViper() {
viper.BindPFlag("BaseURL", flag.Lookup("baseurl")) viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl")) viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
viper.BindPFlag("ViewMode", flag.Lookup("view-mode")) 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(".")
@@ -179,9 +187,11 @@ func handler() http.Handler {
} }
fm := &filemanager.FileManager{ fm := &filemanager.FileManager{
NoAuth: viper.GetBool("NoAuth"), NoAuth: viper.GetBool("NoAuth"),
BaseURL: viper.GetString("BaseURL"), BaseURL: viper.GetString("BaseURL"),
PrefixURL: viper.GetString("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"),

View File

@@ -22,7 +22,13 @@ import (
) )
// Version is the current File Manager version. // Version is the current File Manager version.
const Version = "1.3.2" const (
// Version is the current File Manager version.
Version = "1.3.7"
ListViewMode = "list"
MosaicViewMode = "mosaic"
)
var ( var (
ErrExist = errors.New("the resource already exists") ErrExist = errors.New("the resource already exists")
@@ -66,6 +72,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
@@ -201,6 +211,14 @@ func (m *FileManager) Setup() error {
} }
} }
// TODO: remove this after 1.5
for _, user := range users {
if user.ViewMode != ListViewMode && user.ViewMode != MosaicViewMode {
user.ViewMode = ListViewMode
m.Store.Users.Update(user, "ViewMode")
}
}
m.DefaultUser.Username = "" m.DefaultUser.Username = ""
m.DefaultUser.Password = "" m.DefaultUser.Password = ""
@@ -344,7 +362,7 @@ var DefaultUser = User{
Rules: []*Rule{}, Rules: []*Rule{},
CSS: "", CSS: "",
Admin: true, Admin: true,
Locale: "en", Locale: "",
Scope: ".", Scope: ".",
FileSystem: fileutils.Dir("."), FileSystem: fileutils.Dir("."),
ViewMode: "mosaic", ViewMode: "mosaic",

View File

@@ -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 {

View File

@@ -25,16 +25,13 @@ func Handler(m *fm.FileManager) http.Handler {
if code >= 400 { if code >= 400 {
w.WriteHeader(code) w.WriteHeader(code)
if err == nil { txt := http.StatusText(code)
txt := http.StatusText(code) log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
log.Printf("%v: %v %v\n", r.URL.Path, code, txt) w.Write([]byte(txt + "\n"))
w.Write([]byte(txt))
}
} }
if err != nil { if err != nil {
log.Print(err) log.Print(err)
w.Write([]byte(err.Error()))
} }
}) })
} }
@@ -226,10 +223,13 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string) (int, error)
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, "Version": fm.Version,
"CSS": template.CSS(c.CSS), "CSS": template.CSS(c.CSS),
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
"ReCaptchaKey": c.ReCaptchaKey,
"ReCaptchaSecret": c.ReCaptchaSecret,
} }
if c.StaticGen != nil { if c.StaticGen != nil {
@@ -267,6 +267,7 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
info, err := os.Stat(s.Path) info, err := os.Stat(s.Path)
if err != nil { if err != nil {
c.Store.Share.Delete(s.Hash)
return ErrorToHTTP(err, false), err return ErrorToHTTP(err, false), err
} }

View File

@@ -182,6 +182,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
} }
u.Password = pw u.Password = pw
u.ViewMode = fm.MosaicViewMode
// Saves the user to the database. // Saves the user to the database.
err = c.Store.Users.Save(u) err = c.Store.Users.Save(u)
@@ -268,6 +269,13 @@ 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

18
package-lock.json generated
View File

@@ -8055,15 +8055,6 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true "dev": true
}, },
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"string-length": { "string-length": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
@@ -8100,6 +8091,15 @@
} }
} }
}, },
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",

View File

@@ -1 +1 @@
36b9bba06c64cd83f3994bbb77fc36948d9d2dfe 882c59547968e4fb9a65aa8d1c838409f198e51b