mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Support backup uploads/downloads directly to/from S3.
This commit is contained in:
committed by
Guo Xiang Tan
parent
5039a6c3f1
commit
c29a4dddc1
@@ -1,9 +1,15 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
adminBackups: Ember.inject.controller(),
|
||||
status: Ember.computed.alias("adminBackups.model"),
|
||||
|
||||
@computed
|
||||
localBackupStorage() {
|
||||
return this.siteSettings.backup_location === "local";
|
||||
},
|
||||
|
||||
uploadLabel: function() {
|
||||
return I18n.t("admin.backups.upload.label");
|
||||
}.property(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import PreloadStore from "preload-store";
|
||||
|
||||
const Backup = Discourse.Model.extend({
|
||||
destroy() {
|
||||
@@ -16,9 +15,9 @@ const Backup = Discourse.Model.extend({
|
||||
|
||||
Backup.reopenClass({
|
||||
find() {
|
||||
return PreloadStore.getAndRemove("backups", () =>
|
||||
ajax("/admin/backups.json")
|
||||
).then(backups => backups.map(backup => Backup.create(backup)));
|
||||
return ajax("/admin/backups.json").then(backups =>
|
||||
backups.map(backup => Backup.create(backup))
|
||||
);
|
||||
},
|
||||
|
||||
start(withUploads) {
|
||||
|
||||
@@ -151,6 +151,15 @@ export default Discourse.Route.extend({
|
||||
message: message
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
remoteUploadSuccess() {
|
||||
Backup.find().then(backups => {
|
||||
this.controllerFor("adminBackupsIndex").set(
|
||||
"model",
|
||||
backups.map(backup => Backup.create(backup))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<div class="backup-options">
|
||||
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}}
|
||||
{{#if localBackupStorage}}
|
||||
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}}
|
||||
{{else}}
|
||||
{{backup-uploader done="remoteUploadSuccess"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if site.isReadOnly}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
|
||||
{{else}}
|
||||
@@ -10,7 +15,7 @@
|
||||
<thead>
|
||||
<th width="55%">{{i18n 'admin.backups.columns.filename'}}</th>
|
||||
<th width="10%">{{i18n 'admin.backups.columns.size'}}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each model as |backup|}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<label class="btn {{if addDisabled 'disabled'}}">
|
||||
{{d-icon "upload"}}
|
||||
{{i18n 'admin.watched_words.form.upload'}}
|
||||
<input disabled={{addDisabled}} type="file" accept="text/plain,text/csv" style="visibility: hidden; position: absolute;" />
|
||||
<input class="hidden-upload-field" disabled={{addDisabled}} type="file" accept="text/plain,text/csv" />
|
||||
</label>
|
||||
<br/>
|
||||
<span class="instructions">One word per line</span>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import UploadMixin from "discourse/mixins/upload";
|
||||
|
||||
export default Em.Component.extend(UploadMixin, {
|
||||
tagName: "span",
|
||||
|
||||
@computed("uploading", "uploadProgress")
|
||||
uploadButtonText(uploading, progress) {
|
||||
return uploading
|
||||
? I18n.t("admin.backups.upload.uploading_progress", { progress })
|
||||
: I18n.t("admin.backups.upload.label");
|
||||
},
|
||||
|
||||
validateUploadedFilesOptions() {
|
||||
return { skipValidation: true };
|
||||
},
|
||||
|
||||
uploadDone() {
|
||||
this.sendAction("done");
|
||||
},
|
||||
|
||||
calculateUploadUrl() {
|
||||
return "";
|
||||
},
|
||||
|
||||
uploadOptions() {
|
||||
return {
|
||||
type: "PUT",
|
||||
dataType: "xml",
|
||||
autoUpload: false
|
||||
};
|
||||
},
|
||||
|
||||
_init: function() {
|
||||
const $upload = this.$();
|
||||
|
||||
$upload.on("fileuploadadd", (e, data) => {
|
||||
ajax("/admin/backups/upload_url", {
|
||||
data: { filename: data.files[0].name }
|
||||
}).then(result => {
|
||||
if (!result.success) {
|
||||
bootbox.alert(result.message);
|
||||
} else {
|
||||
data.url = result.url;
|
||||
data.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
}.on("didInsertElement")
|
||||
});
|
||||
@@ -225,6 +225,7 @@ export function validateUploadedFiles(files, opts) {
|
||||
}
|
||||
|
||||
export function validateUploadedFile(file, opts) {
|
||||
if (opts.skipValidation) return true;
|
||||
if (!authorizesOneOrMoreExtensions()) return false;
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
displayErrorForUpload,
|
||||
validateUploadedFiles
|
||||
} from "discourse/lib/utilities";
|
||||
import getUrl from "discourse-common/lib/get-url";
|
||||
|
||||
export default Em.Mixin.create({
|
||||
uploading: false,
|
||||
@@ -15,13 +16,25 @@ export default Em.Mixin.create({
|
||||
return {};
|
||||
},
|
||||
|
||||
calculateUploadUrl() {
|
||||
return (
|
||||
getUrl(this.getWithDefault("uploadUrl", "/uploads")) +
|
||||
".json?client_id=" +
|
||||
this.messageBus.clientId +
|
||||
"&authenticity_token=" +
|
||||
encodeURIComponent(Discourse.Session.currentProp("csrfToken"))
|
||||
);
|
||||
},
|
||||
|
||||
uploadOptions() {
|
||||
return {};
|
||||
},
|
||||
|
||||
_initialize: function() {
|
||||
const $upload = this.$(),
|
||||
csrf = Discourse.Session.currentProp("csrfToken"),
|
||||
uploadUrl = Discourse.getURL(
|
||||
this.getWithDefault("uploadUrl", "/uploads")
|
||||
),
|
||||
reset = () => this.setProperties({ uploading: false, uploadProgress: 0 });
|
||||
const $upload = this.$();
|
||||
const reset = () =>
|
||||
this.setProperties({ uploading: false, uploadProgress: 0 });
|
||||
const maxFiles = this.getWithDefault("maxFiles", 10);
|
||||
|
||||
$upload.on("fileuploaddone", (e, data) => {
|
||||
let upload = data.result;
|
||||
@@ -29,20 +42,21 @@ export default Em.Mixin.create({
|
||||
reset();
|
||||
});
|
||||
|
||||
$upload.fileupload({
|
||||
url:
|
||||
uploadUrl +
|
||||
".json?client_id=" +
|
||||
this.messageBus.clientId +
|
||||
"&authenticity_token=" +
|
||||
encodeURIComponent(csrf),
|
||||
dataType: "json",
|
||||
dropZone: $upload,
|
||||
pasteZone: $upload
|
||||
});
|
||||
$upload.fileupload(
|
||||
_.merge(
|
||||
{
|
||||
url: this.calculateUploadUrl(),
|
||||
dataType: "json",
|
||||
replaceFileInput: false,
|
||||
dropZone: $upload,
|
||||
pasteZone: $upload
|
||||
},
|
||||
this.uploadOptions()
|
||||
)
|
||||
);
|
||||
|
||||
$upload.on("fileuploaddrop", (e, data) => {
|
||||
if (data.files.length > 10) {
|
||||
if (data.files.length > maxFiles) {
|
||||
bootbox.alert(I18n.t("post.errors.too_many_dragged_and_dropped_files"));
|
||||
return false;
|
||||
} else {
|
||||
@@ -56,7 +70,8 @@ export default Em.Mixin.create({
|
||||
this.validateUploadedFilesOptions()
|
||||
);
|
||||
const isValid = validateUploadedFiles(data.files, opts);
|
||||
let form = { type: this.get("type") };
|
||||
const type = this.get("type");
|
||||
let form = type ? { type } : {};
|
||||
if (this.get("data")) {
|
||||
form = $.extend(form, this.get("data"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<label class="btn" disabled={{uploading}} title="{{i18n 'user.change_avatar.upload_title'}}">
|
||||
{{d-icon "picture-o"}} {{uploadButtonText}}
|
||||
<input disabled={{uploading}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" />
|
||||
</label>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<label class="btn" disabled={{uploading}} title="{{i18n 'admin.backups.upload.title'}}">
|
||||
{{d-icon "upload"}} {{uploadButtonText}}
|
||||
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept=".gz" />
|
||||
</label>
|
||||
@@ -1,6 +1,6 @@
|
||||
<label class="btn" disabled={{uploadButtonDisabled}}>
|
||||
{{d-icon "upload"}} {{uploadButtonText}}
|
||||
<input disabled={{uploading}} type="file" accept=".csv" style="visibility: hidden; position: absolute;" />
|
||||
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept=".csv" />
|
||||
</label>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<label class="btn btn-primary {{if addDisabled 'disabled'}}">
|
||||
{{d-icon "plus"}}
|
||||
{{i18n 'admin.emoji.add'}}
|
||||
<input disabled={{addDisabled}} type="file" accept=".png,.gif" style="visibility: hidden; position: absolute;" />
|
||||
<input class="hidden-upload-field" disabled={{addDisabled}} type="file" accept=".png,.gif" />
|
||||
</label>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="image-upload-controls">
|
||||
<label class="btn pad-left no-text {{if uploading 'disabled'}}">
|
||||
{{d-icon "picture-o"}}
|
||||
<input disabled={{uploading}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" />
|
||||
</label>
|
||||
{{#if backgroundStyle}}
|
||||
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "trash-o"}}</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<label class="btn" disabled={{uploading}} title="{{i18n "admin.site_settings.uploaded_image_list.upload.title"}}">
|
||||
{{d-icon "picture-o"}} {{uploadButtonText}}
|
||||
<input disabled={{uploading}} type="file" accept="image/*" multiple style="visibility: hidden; position: absolute;" />
|
||||
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" multiple />
|
||||
</label>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
{{d-icon "picture-o"}}
|
||||
{{/if}}
|
||||
|
||||
<input disabled={{uploading}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
<input class="wizard-hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" />
|
||||
</label>
|
||||
|
||||
@@ -14,3 +14,8 @@
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-upload-field {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@@ -328,6 +328,11 @@ body.wizard {
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-hidden-upload-field {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.wizard-step-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
Reference in New Issue
Block a user