FEATURE: new 'backup_frequency' site setting
This commit is contained in:
parent
9156d6cd9d
commit
15418f3d44
|
@ -1,20 +1,20 @@
|
|||
export default Ember.ArrayController.extend({
|
||||
needs: ["adminBackups"],
|
||||
status: Em.computed.alias("controllers.adminBackups"),
|
||||
isOperationRunning: Em.computed.alias("status.isOperationRunning"),
|
||||
restoreDisabled: Em.computed.alias("status.restoreDisabled"),
|
||||
isOperationRunning: Em.computed.alias("status.model.isOperationRunning"),
|
||||
restoreDisabled: Em.computed.alias("status.model.restoreDisabled"),
|
||||
|
||||
uploadLabel: function() { return I18n.t("admin.backups.upload.label"); }.property(),
|
||||
|
||||
restoreTitle: function() {
|
||||
if (!this.get('status.allowRestore')) {
|
||||
if (!this.get('status.model.allowRestore')) {
|
||||
return "admin.backups.operations.restore.is_disabled";
|
||||
} else if (this.get("status.isOperationRunning")) {
|
||||
} else if (this.get("status.model.isOperationRunning")) {
|
||||
return "admin.backups.operations.is_running";
|
||||
} else {
|
||||
return "admin.backups.operations.restore.title";
|
||||
}
|
||||
}.property("status.{allowRestore,isOperationRunning}"),
|
||||
}.property("status.model.{allowRestore,isOperationRunning}"),
|
||||
|
||||
actions: {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default Ember.ObjectController.extend({
|
||||
noOperationIsRunning: Em.computed.not("isOperationRunning"),
|
||||
rollbackEnabled: Em.computed.and("canRollback", "restoreEnabled", "noOperationIsRunning"),
|
||||
noOperationIsRunning: Em.computed.not("model.isOperationRunning"),
|
||||
rollbackEnabled: Em.computed.and("model.canRollback", "model.restoreEnabled", "noOperationIsRunning"),
|
||||
rollbackDisabled: Em.computed.not("rollbackEnabled")
|
||||
});
|
||||
|
|
|
@ -10,14 +10,14 @@ export default Discourse.Route.extend({
|
|||
|
||||
_processLogMessage(log) {
|
||||
if (log.message === "[STARTED]") {
|
||||
this.controllerFor("adminBackups").set("isOperationRunning", true);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", true);
|
||||
this.controllerFor("adminBackupsLogs").clear();
|
||||
} else if (log.message === "[FAILED]") {
|
||||
this.controllerFor("adminBackups").set("isOperationRunning", false);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
bootbox.alert(I18n.t("admin.backups.operations.failed", { operation: log.operation }));
|
||||
} else if (log.message === "[SUCCESS]") {
|
||||
Discourse.User.currentProp("hideReadOnlyAlert", false);
|
||||
this.controllerFor("adminBackups").set("isOperationRunning", false);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
if (log.operation === "restore") {
|
||||
// redirect to homepage when the restore is done (session might be lost)
|
||||
window.location.pathname = Discourse.getURL("/");
|
||||
|
@ -30,7 +30,7 @@ export default Discourse.Route.extend({
|
|||
model() {
|
||||
return PreloadStore.getAndRemove("operations_status", function() {
|
||||
return Discourse.ajax("/admin/backups/status.json");
|
||||
}).then(function (status) {
|
||||
}).then(status => {
|
||||
return Discourse.BackupStatus.create({
|
||||
isOperationRunning: status.is_operation_running,
|
||||
canRollback: status.can_rollback,
|
||||
|
@ -99,7 +99,7 @@ export default Discourse.Route.extend({
|
|||
function(confirmed) {
|
||||
if (confirmed) {
|
||||
Discourse.Backup.cancel().then(function() {
|
||||
self.controllerFor("adminBackups").set("isOperationRunning", false);
|
||||
self.modelFor("adminBackups").set("isOperationRunning", false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{#if canRollback}}
|
||||
{{#if model.canRollback}}
|
||||
{{d-button action="rollback"
|
||||
class="btn-rollback"
|
||||
label="admin.backups.operations.rollback.label"
|
||||
|
@ -14,7 +14,7 @@
|
|||
icon="ambulance"
|
||||
disabled=rollbackDisabled}}
|
||||
{{/if}}
|
||||
{{#if isOperationRunning}}
|
||||
{{#if model.isOperationRunning}}
|
||||
{{d-button action="cancelOperation"
|
||||
class="btn-danger"
|
||||
title="admin.backups.operations.cancel.title"
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
<div class="pull-right">
|
||||
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}}
|
||||
{{#if site.isReadOnly}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=model.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
|
||||
{{else}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=model.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</th>
|
||||
|
@ -20,12 +20,12 @@
|
|||
<td>
|
||||
<div class="pull-right">
|
||||
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n 'admin.backups.operations.download.title'}}">{{fa-icon "download"}}{{i18n 'admin.backups.operations.download.label'}}</a>
|
||||
{{#if isOperationRunning}}
|
||||
{{#if model.isOperationRunning}}
|
||||
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{else}}
|
||||
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -5,20 +5,20 @@ export default Discourse.View.extend({
|
|||
|
||||
_initialize: function() { this._reset(); }.on("init"),
|
||||
|
||||
_reset: function() {
|
||||
_reset() {
|
||||
this.setProperties({ formattedLogs: "", index: 0 });
|
||||
},
|
||||
|
||||
_updateFormattedLogs: Discourse.debounce(function() {
|
||||
var logs = this.get("controller.model");
|
||||
const logs = this.get("controller.model");
|
||||
if (logs.length === 0) {
|
||||
this._reset(); // reset the cached logs whenever the model is reset
|
||||
} else {
|
||||
// do the log formatting only once for HELLish performance
|
||||
var formattedLogs = this.get("formattedLogs");
|
||||
for (var i = this.get("index"), length = logs.length; i < length; i++) {
|
||||
var date = logs[i].get("timestamp"),
|
||||
message = Handlebars.Utils.escapeExpression(logs[i].get("message"));
|
||||
let formattedLogs = this.get("formattedLogs");
|
||||
for (let i = this.get("index"), length = logs.length; i < length; i++) {
|
||||
const date = logs[i].get("timestamp"),
|
||||
message = Handlebars.Utils.escapeExpression(logs[i].get("message"));
|
||||
formattedLogs += "[" + date + "] " + message + "\n";
|
||||
}
|
||||
// update the formatted logs & cache index
|
||||
|
@ -28,8 +28,8 @@ export default Discourse.View.extend({
|
|||
}
|
||||
}, 150).observes("controller.model.@each"),
|
||||
|
||||
render: function(buffer) {
|
||||
var formattedLogs = this.get("formattedLogs");
|
||||
render(buffer) {
|
||||
const formattedLogs = this.get("formattedLogs");
|
||||
if (formattedLogs && formattedLogs.length > 0) {
|
||||
buffer.push("<pre>");
|
||||
buffer.push(formattedLogs);
|
||||
|
@ -38,13 +38,13 @@ export default Discourse.View.extend({
|
|||
buffer.push("<p>" + I18n.t("admin.backups.logs.none") + "</p>");
|
||||
}
|
||||
// add a loading indicator
|
||||
if (this.get("controller.status.isOperationRunning")) {
|
||||
if (this.get("controller.status.model.isOperationRunning")) {
|
||||
buffer.push(renderSpinner('small'));
|
||||
}
|
||||
},
|
||||
|
||||
_forceScrollToBottom: function() {
|
||||
var $div = this.$()[0];
|
||||
const $div = this.$()[0];
|
||||
$div.scrollTop = $div.scrollHeight;
|
||||
}.on("didInsertElement")
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
require "backup_restore/backup_restore"
|
||||
|
||||
module Jobs
|
||||
class CreateDailyBackup < Jobs::Base
|
||||
class CreateBackup < Jobs::Base
|
||||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
return unless SiteSetting.backup_daily?
|
||||
return unless SiteSetting.backups_enabled?
|
||||
BackupRestore.backup!(Discourse.system_user.id, publish_to_message_bus: false)
|
||||
end
|
||||
end
|
|
@ -5,8 +5,14 @@ module Jobs
|
|||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
return unless SiteSetting.backup_daily?
|
||||
Jobs.enqueue_in(rand(10.minutes), :create_daily_backup)
|
||||
return unless SiteSetting.backups_enabled?
|
||||
|
||||
if latest_backup = Backup.all[0]
|
||||
date = Date.parse(latest_backup.filename[/\d{4}-\d{2}-\d{2}/])
|
||||
return if date + SiteSetting.backup_frequency.days > Time.now
|
||||
end
|
||||
|
||||
Jobs.enqueue_in(rand(10.minutes), :create_backup)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -111,6 +111,10 @@ class SiteSetting < ActiveRecord::Base
|
|||
false
|
||||
end
|
||||
|
||||
def self.backups_enabled?
|
||||
SiteSetting.backup_frequency > 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -938,7 +938,7 @@ en:
|
|||
|
||||
allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to restore a backup"
|
||||
maximum_backups: "The maximum amount of backups to keep on disk. Older backups are automatically deleted"
|
||||
backup_daily: "Automatically create a site backup once a day."
|
||||
backup_frequency: "How frequently we create a site backup, in days."
|
||||
enable_s3_backups: "Upload backups to S3 when complete. IMPORTANT: requires valid S3 credentials entered in Files settings."
|
||||
s3_backup_bucket: "The remote bucket to hold backups. WARNING: Make sure it is a private bucket."
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ required:
|
|||
client: true
|
||||
default: '/images/d-logo-sketch-small.png'
|
||||
digest_logo_url:
|
||||
client: false
|
||||
default: ''
|
||||
mobile_logo_url:
|
||||
client: true
|
||||
|
@ -70,7 +69,6 @@ basic:
|
|||
default: 5
|
||||
min: 0
|
||||
limit_suggested_to_category:
|
||||
client: false
|
||||
default: false
|
||||
default_external_links_in_new_tab: false
|
||||
track_external_right_clicks:
|
||||
|
@ -789,19 +787,17 @@ legal:
|
|||
|
||||
backups:
|
||||
allow_restore:
|
||||
client: false
|
||||
default: false
|
||||
maximum_backups:
|
||||
client: true
|
||||
default: 7
|
||||
backup_daily:
|
||||
client: false
|
||||
default: false
|
||||
backup_frequency:
|
||||
min: 0
|
||||
max: 7
|
||||
default: 1
|
||||
enable_s3_backups:
|
||||
client: false
|
||||
default: false
|
||||
s3_backup_bucket:
|
||||
client: false
|
||||
default: ''
|
||||
regex: "^[^A-Z_.]+$" # can't use '.' when using HTTPS
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require_dependency 'jobs/regular/create_daily_backup'
|
||||
|
||||
describe Jobs::CreateDailyBackup do
|
||||
it "does nothing when daily backups are disabled" do
|
||||
SiteSetting.stubs(:backup_daily?).returns(false)
|
||||
BackupRestore.expects(:backup!).never
|
||||
Jobs::CreateDailyBackup.new.execute({})
|
||||
end
|
||||
|
||||
it "calls `backup!` when the daily backups are enabled" do
|
||||
SiteSetting.stubs(:backup_daily?).returns(true)
|
||||
BackupRestore.expects(:backup!).with(Discourse.system_user.id, { publish_to_message_bus: false }).once
|
||||
Jobs::CreateDailyBackup.new.execute({})
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue