FEATURE: new 'backup_frequency' site setting

This commit is contained in:
Régis Hanol 2015-08-07 17:34:58 +02:00
parent 9156d6cd9d
commit 15418f3d44
12 changed files with 48 additions and 60 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -111,6 +111,10 @@ class SiteSetting < ActiveRecord::Base
false
end
def self.backups_enabled?
SiteSetting.backup_frequency > 0
end
end
# == Schema Information

View file

@ -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."

View file

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

View file

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