mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-28 06:22:24 +00:00
Resolve "The "Try to reconnect" modal shortly appears when trying to download a user file"
This commit is contained in:
parent
7b47b5a470
commit
921e3dfc08
12 changed files with 178 additions and 34 deletions
backend
changelog.mddeploy/heroku
docs/getting-started
web-frontend/modules
|
@ -28,3 +28,5 @@ click==7.1.2
|
||||||
cryptography==3.4.8
|
cryptography==3.4.8
|
||||||
antlr4-python3-runtime==4.8.0
|
antlr4-python3-runtime==4.8.0
|
||||||
tqdm==4.62.3
|
tqdm==4.62.3
|
||||||
|
boto3==1.20.38
|
||||||
|
django-storages==1.12.3
|
||||||
|
|
|
@ -266,6 +266,27 @@ SPECTACULAR_SETTINGS = {
|
||||||
# The storage must always overwrite existing files.
|
# The storage must always overwrite existing files.
|
||||||
DEFAULT_FILE_STORAGE = "baserow.core.storage.OverwriteFileSystemStorage"
|
DEFAULT_FILE_STORAGE = "baserow.core.storage.OverwriteFileSystemStorage"
|
||||||
|
|
||||||
|
# Optional S3 storage configuration
|
||||||
|
if os.getenv("AWS_ACCESS_KEY_ID", "") != "":
|
||||||
|
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||||
|
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
|
||||||
|
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
|
||||||
|
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
|
||||||
|
AWS_S3_OBJECT_PARAMETERS = {
|
||||||
|
"CacheControl": "max-age=86400",
|
||||||
|
}
|
||||||
|
AWS_S3_FILE_OVERWRITE = True
|
||||||
|
AWS_DEFAULT_ACL = "public-read"
|
||||||
|
|
||||||
|
if os.getenv("AWS_S3_REGION_NAME", "") != "":
|
||||||
|
AWS_S3_REGION_NAME = os.getenv("AWS_S3_REGION_NAME")
|
||||||
|
|
||||||
|
if os.getenv("AWS_S3_ENDPOINT_URL", "") != "":
|
||||||
|
AWS_S3_ENDPOINT_URL = os.getenv("AWS_S3_ENDPOINT_URL")
|
||||||
|
|
||||||
|
if os.getenv("AWS_S3_CUSTOM_DOMAIN", "") != "":
|
||||||
|
AWS_S3_CUSTOM_DOMAIN = os.getenv("AWS_S3_CUSTOM_DOMAIN")
|
||||||
|
|
||||||
MJML_BACKEND_MODE = "tcpserver"
|
MJML_BACKEND_MODE = "tcpserver"
|
||||||
MJML_TCPSERVERS = [
|
MJML_TCPSERVERS = [
|
||||||
(os.getenv("MJML_SERVER_HOST", "mjml"), int(os.getenv("MJML_SERVER_PORT", 28101))),
|
(os.getenv("MJML_SERVER_HOST", "mjml"), int(os.getenv("MJML_SERVER_PORT", 28101))),
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
* Fixed migration failing when upgrading a version of Baserow installed using Postgres
|
* Fixed migration failing when upgrading a version of Baserow installed using Postgres
|
||||||
10 or lower.
|
10 or lower.
|
||||||
|
* Fixed download/preview files from another origin
|
||||||
|
|
||||||
## Released (2022-01-13)
|
## Released (2022-01-13)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ import os
|
||||||
import ssl
|
import ssl
|
||||||
import dj_database_url
|
import dj_database_url
|
||||||
|
|
||||||
INSTALLED_APPS = INSTALLED_APPS + ["storages"]
|
|
||||||
|
|
||||||
MEDIA_ROOT = "/baserow/media"
|
MEDIA_ROOT = "/baserow/media"
|
||||||
|
|
||||||
MJML_BACKEND_MODE = "cmd"
|
MJML_BACKEND_MODE = "cmd"
|
||||||
|
@ -39,24 +37,3 @@ CELERY_REDIS_MAX_CONNECTIONS = min(
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": dj_database_url.parse(os.environ["DATABASE_URL"], conn_max_age=600)
|
"default": dj_database_url.parse(os.environ["DATABASE_URL"], conn_max_age=600)
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.getenv("AWS_ACCESS_KEY_ID", "") != "":
|
|
||||||
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
|
||||||
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
|
|
||||||
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
|
|
||||||
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
|
|
||||||
AWS_S3_OBJECT_PARAMETERS = {
|
|
||||||
"CacheControl": "max-age=86400",
|
|
||||||
"ContentDisposition": "attachment",
|
|
||||||
}
|
|
||||||
AWS_S3_FILE_OVERWRITE = True
|
|
||||||
AWS_DEFAULT_ACL = "public-read"
|
|
||||||
|
|
||||||
if os.getenv("AWS_S3_REGION_NAME", "") != "":
|
|
||||||
AWS_S3_REGION_NAME = os.getenv("AWS_S3_REGION_NAME")
|
|
||||||
|
|
||||||
if os.getenv("AWS_S3_ENDPOINT_URL", "") != "":
|
|
||||||
AWS_S3_ENDPOINT_URL = os.getenv("AWS_S3_ENDPOINT_URL")
|
|
||||||
|
|
||||||
if os.getenv("AWS_S3_CUSTOM_DOMAIN", "") != "":
|
|
||||||
AWS_S3_CUSTOM_DOMAIN = os.getenv("AWS_S3_CUSTOM_DOMAIN")
|
|
||||||
|
|
|
@ -90,6 +90,10 @@ are accepted.
|
||||||
* `DATABASE_PASSWORD` (default `baserow`): The password for the PostgreSQL database.
|
* `DATABASE_PASSWORD` (default `baserow`): The password for the PostgreSQL database.
|
||||||
* `DATABASE_HOST` (default `db`): The hostname of the PostgreSQL server.
|
* `DATABASE_HOST` (default `db`): The hostname of the PostgreSQL server.
|
||||||
* `DATABASE_PORT` (default `5432`): The port of the PostgreSQL server.
|
* `DATABASE_PORT` (default `5432`): The port of the PostgreSQL server.
|
||||||
|
* `DOWNLOAD_FILE_VIA_XHR` (default `0`): Set to `1` to force download links to
|
||||||
|
download files via XHR query to bypass `Content-Disposition: inline` that
|
||||||
|
can't be overridden in another way. If your files are stored under another
|
||||||
|
origin, you also must add CORS headers to your server.
|
||||||
* `MJML_SERVER_HOST` (default `mjml`): The hostname of the MJML TCP server. In the
|
* `MJML_SERVER_HOST` (default `mjml`): The hostname of the MJML TCP server. In the
|
||||||
development environment we use the `liminspace/mjml-tcpserver:0.10` image.
|
development environment we use the `liminspace/mjml-tcpserver:0.10` image.
|
||||||
* `MJML_SERVER_PORT` (default `28101`): The port of the MJML TCP server.
|
* `MJML_SERVER_PORT` (default `28101`): The port of the MJML TCP server.
|
||||||
|
|
|
@ -42,7 +42,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-field-modal__body {
|
.file-field-modal__body {
|
||||||
@include absolute($file-field-modal-head-height, 0, $file-field-modal-foot-height, 0);
|
@include absolute(
|
||||||
|
$file-field-modal-head-height,
|
||||||
|
0,
|
||||||
|
$file-field-modal-foot-height,
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-field-modal__body-nav {
|
.file-field-modal__body-nav {
|
||||||
|
@ -75,7 +80,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
@include absolute(0, $file-field-modal-body-nav-width, 0, $file-field-modal-body-nav-width);
|
@include absolute(
|
||||||
|
0,
|
||||||
|
$file-field-modal-body-nav-width,
|
||||||
|
0,
|
||||||
|
$file-field-modal-body-nav-width
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-field-modal__preview-icon {
|
.file-field-modal__preview-icon {
|
||||||
|
@ -166,4 +176,20 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $color-neutral-700;
|
background-color: $color-neutral-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.file-field-modal__action--loading {
|
||||||
|
// Prevent interactions while loading file
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
@include loading(16px);
|
||||||
|
@include absolute(8px, 8px, auto, auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .fas {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
99
web-frontend/modules/core/components/DownloadLink.vue
Normal file
99
web-frontend/modules/core/components/DownloadLink.vue
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<template>
|
||||||
|
<a
|
||||||
|
v-if="!downloadXHR"
|
||||||
|
:href="`${url}?dl=${filename}`"
|
||||||
|
target="_blank"
|
||||||
|
:download="filename"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="`${url}`"
|
||||||
|
target="_blank"
|
||||||
|
:download="filename"
|
||||||
|
:class="{ [loadingClass]: loading }"
|
||||||
|
@click="onClick($event)"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DownloadLink',
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
onError: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
loadingClass: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { loading: false }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
downloadXHR() {
|
||||||
|
return this.$env.DOWNLOAD_FILE_VIA_XHR === '1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async download() {
|
||||||
|
this.loading = true
|
||||||
|
// We are using fetch here to avoid extra header
|
||||||
|
// as we need to add them to CORS later
|
||||||
|
const response = await fetch(this.url)
|
||||||
|
const blob = await response.blob()
|
||||||
|
const data = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
|
||||||
|
// Create temporary anchor element to trigger the download
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.style = 'display: none'
|
||||||
|
a.href = data
|
||||||
|
a.target = '_blank'
|
||||||
|
a.download = this.filename
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.onclick = (e) => {
|
||||||
|
// prevent modal/whatever closing
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
a.click()
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
// Remove the element
|
||||||
|
document.body.removeChild(a)
|
||||||
|
// Release resource on after triggering the download
|
||||||
|
window.URL.revokeObjectURL(data)
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
onClick(event) {
|
||||||
|
if (this.downloadXHR) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.download().catch((error) => {
|
||||||
|
// In any case, to be sure the loading animation will not last
|
||||||
|
this.loading = false
|
||||||
|
if (this.onError) {
|
||||||
|
this.onError(error)
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -141,7 +141,7 @@ export default {
|
||||||
},
|
},
|
||||||
trashDuration() {
|
trashDuration() {
|
||||||
const hours = this.$env.HOURS_UNTIL_TRASH_PERMANENTLY_DELETED
|
const hours = this.$env.HOURS_UNTIL_TRASH_PERMANENTLY_DELETED
|
||||||
return moment().subtract(hours, 'hours').fromNow().replace('ago', '')
|
return moment().subtract(hours, 'hours').fromNow(true)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -51,6 +51,15 @@ export default function CoreModule(options) {
|
||||||
key: 'INITIAL_TABLE_DATA_LIMIT',
|
key: 'INITIAL_TABLE_DATA_LIMIT',
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Set to `1` to force download links to download files via XHR query
|
||||||
|
// to bypass `Content-Disposition: inline` that can't be overridden
|
||||||
|
// in another way.
|
||||||
|
// If your files are stored under another origin, you also
|
||||||
|
// must add CORS headers to your server.
|
||||||
|
key: 'DOWNLOAD_FILE_VIA_XHR',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// If you change this default please also update the default for the
|
// If you change this default please also update the default for the
|
||||||
// backend found in src/baserow/config/settings/base.py:321
|
// backend found in src/baserow/config/settings/base.py:321
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Error from '@baserow/modules/core/components/Error'
|
||||||
import SwitchInput from '@baserow/modules/core/components/SwitchInput'
|
import SwitchInput from '@baserow/modules/core/components/SwitchInput'
|
||||||
import Copied from '@baserow/modules/core/components/Copied'
|
import Copied from '@baserow/modules/core/components/Copied'
|
||||||
import MarkdownIt from '@baserow/modules/core/components/MarkdownIt'
|
import MarkdownIt from '@baserow/modules/core/components/MarkdownIt'
|
||||||
|
import DownloadLink from '@baserow/modules/core/components/DownloadLink'
|
||||||
|
|
||||||
import lowercase from '@baserow/modules/core/filters/lowercase'
|
import lowercase from '@baserow/modules/core/filters/lowercase'
|
||||||
import uppercase from '@baserow/modules/core/filters/uppercase'
|
import uppercase from '@baserow/modules/core/filters/uppercase'
|
||||||
|
@ -37,6 +38,7 @@ Vue.component('Error', Error)
|
||||||
Vue.component('SwitchInput', SwitchInput)
|
Vue.component('SwitchInput', SwitchInput)
|
||||||
Vue.component('Copied', Copied)
|
Vue.component('Copied', Copied)
|
||||||
Vue.component('MarkdownIt', MarkdownIt)
|
Vue.component('MarkdownIt', MarkdownIt)
|
||||||
|
Vue.component('DownloadLink', DownloadLink)
|
||||||
|
|
||||||
Vue.filter('lowercase', lowercase)
|
Vue.filter('lowercase', lowercase)
|
||||||
Vue.filter('uppercase', uppercase)
|
Vue.filter('uppercase', uppercase)
|
||||||
|
|
|
@ -25,16 +25,18 @@
|
||||||
>
|
>
|
||||||
{{ $t('exportTableLoadingBar.export') }}
|
{{ $t('exportTableLoadingBar.export') }}
|
||||||
</button>
|
</button>
|
||||||
<a
|
<DownloadLink
|
||||||
v-else
|
v-else
|
||||||
class="
|
class="
|
||||||
button button--large button--success
|
button button--large button--success
|
||||||
export-table-modal__export-button
|
export-table-modal__export-button
|
||||||
"
|
"
|
||||||
:href="`${job.url}?dl=${filename}`"
|
:url="job.url"
|
||||||
|
:filename="filename"
|
||||||
|
:loading-class="'button--loading'"
|
||||||
>
|
>
|
||||||
{{ $t('exportTableLoadingBar.download') }}
|
{{ $t('exportTableLoadingBar.download') }}
|
||||||
</a>
|
</DownloadLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -86,12 +86,13 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-if="preview" class="file-field-modal__actions">
|
<ul v-if="preview" class="file-field-modal__actions">
|
||||||
<a
|
<DownloadLink
|
||||||
:href="preview.url + `?dl=${preview.visible_name}`"
|
|
||||||
class="file-field-modal__action"
|
class="file-field-modal__action"
|
||||||
>
|
:url="preview.url"
|
||||||
<i class="fas fa-download"></i>
|
:filename="preview.visible_name"
|
||||||
</a>
|
:loading-class="'file-field-modal__action--loading'"
|
||||||
|
><i class="fas fa-download"></i
|
||||||
|
></DownloadLink>
|
||||||
<a
|
<a
|
||||||
v-if="!readOnly"
|
v-if="!readOnly"
|
||||||
class="file-field-modal__action"
|
class="file-field-modal__action"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue