Skip to content

Commit

Permalink
FEATURE: Improve backup stats on admin dashboard
Browse files Browse the repository at this point in the history
* Dashboard doesn't timeout anymore when Amazon S3 is used for backups
* Storage stats are now a proper report with the same caching rules
* Changing the backup_location, s3_backup_bucket or creating and deleting backups removes the report from the cache
* It shows the number of backups and the backup location
* It shows the used space for the correct backup location instead of always showing used space on local storage
* It shows the date of the last backup as relative date
  • Loading branch information
gschlager committed Dec 17, 2018
1 parent 040ddec commit 1a8ca68
Show file tree
Hide file tree
Showing 20 changed files with 224 additions and 174 deletions.
@@ -0,0 +1,40 @@
import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";

export default Ember.Component.extend({
classNames: ["admin-report-storage-stats"],

backupLocation: setting("backup_location"),
backupStats: Ember.computed.alias("model.data.backups"),
uploadStats: Ember.computed.alias("model.data.uploads"),

@computed("backupStats")
showBackupStats(stats) {
return stats && this.currentUser.admin;
},

@computed("backupLocation")
backupLocationName(backupLocation) {
return I18n.t(`admin.backups.location.${backupLocation}`);
},

@computed("backupStats.used_bytes")
usedBackupSpace(bytes) {
return I18n.toHumanSize(bytes);
},

@computed("backupStats.free_bytes")
freeBackupSpace(bytes) {
return I18n.toHumanSize(bytes);
},

@computed("uploadStats.used_bytes")
usedUploadSpace(bytes) {
return I18n.toHumanSize(bytes);
},

@computed("uploadStats.free_bytes")
freeUploadSpace(bytes) {
return I18n.toHumanSize(bytes);
}
});
Expand Up @@ -16,12 +16,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
isLoading: false,
dashboardFetchedAt: null,
exceptionController: Ember.inject.controller("exception"),
diskSpace: Ember.computed.alias("model.attributes.disk_space"),
logSearchQueriesEnabled: setting("log_search_queries"),
lastBackupTakenAt: Ember.computed.alias(
"model.attributes.last_backup_taken_at"
),
shouldDisplayDurability: Ember.computed.and("diskSpace"),
basePath: Discourse.BaseUri,

@computed
Expand Down Expand Up @@ -87,6 +82,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, {

usersByTypeReport: staticReport("users_by_type"),
usersByTrustLevelReport: staticReport("users_by_trust_level"),
storageReport: staticReport("storage_report"),

fetchDashboard() {
if (this.get("isLoading")) return;
Expand Down Expand Up @@ -129,13 +125,6 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
.format("LLL");
},

@computed("lastBackupTakenAt")
backupTimestamp(lastBackupTakenAt) {
return moment(lastBackupTakenAt)
.tz(moment.tz.guess())
.format("LLL");
},

_reportsForPeriodURL(period) {
return Discourse.getURL(`/admin?period=${period}`);
}
Expand Down
Expand Up @@ -4,7 +4,6 @@ import AdminUser from "admin/models/admin-user";
import computed from "ember-addons/ember-computed-decorators";

const ATTRIBUTES = [
"disk_space",
"admins",
"moderators",
"silenced",
Expand Down
@@ -1,6 +1,6 @@
import { ajax } from "discourse/lib/ajax";

const GENERAL_ATTRIBUTES = ["disk_space", "updated_at", "last_backup_taken_at"];
const GENERAL_ATTRIBUTES = ["updated_at"];

const AdminDashboardNext = Discourse.Model.extend({});

Expand Down
@@ -0,0 +1,33 @@
{{#if showBackupStats}}
<div class="backups">
<h3 class="storage-stats-title">
<a href="{{get-url '/admin/backups'}}">{{d-icon "archive"}} {{i18n "admin.dashboard.backups"}}</a>
</h3>
<p>
{{#if backupStats.free_bytes}}
{{i18n "admin.dashboard.space_used_and_free" usedSize=usedBackupSpace freeSize=freeBackupSpace}}
{{else}}
{{i18n "admin.dashboard.space_used" usedSize=usedBackupSpace}}
{{/if}}

<br>
{{i18n "admin.dashboard.backup_count" count=backupStats.count location=backupLocationName}}

{{#if backupStats.last_backup_taken_at}}
<br>
{{{i18n "admin.dashboard.lastest_backup" date=(format-date backupStats.last_backup_taken_at leaveAgo="true")}}}
{{/if}}
</p>
</div>
{{/if}}

<div class="uploads">
<h3 class="storage-stats-title">{{d-icon "upload"}} {{i18n "admin.dashboard.uploads"}}</h3>
<p>
{{#if uploadStats.free_bytes}}
{{i18n "admin.dashboard.space_used_and_free" usedSize=usedUploadSpace freeSize=freeUploadSpace}}
{{else}}
{{i18n "admin.dashboard.space_used" usedSize=usedUploadSpace}}
{{/if}}
</p>
</div>
55 changes: 15 additions & 40 deletions app/assets/javascripts/admin/templates/dashboard_next_general.hbs
Expand Up @@ -103,51 +103,26 @@
{{/conditional-loading-section}}
</div>

{{#conditional-loading-section isLoading=isLoading title=(i18n "admin.dashboard.backups")}}
<div class="misc">

{{#if shouldDisplayDurability}}
<div class="durability">
{{#if currentUser.admin}}
<div class="backups">
<h3 class="durability-title">
<a href="{{get-url '/admin/backups'}}">{{d-icon "archive"}} {{i18n "admin.dashboard.backups"}}</a>
</h3>
<p>
{{diskSpace.backups_used}} ({{i18n "admin.dashboard.space_free" size=diskSpace.backups_free}})

{{#if lastBackupTakenAt}}
<br />
{{{i18n "admin.dashboard.lastest_backup" date=backupTimestamp}}}
{{/if}}
</p>
</div>
{{/if}}

<div class="uploads">
<h3 class="durability-title">{{d-icon "upload"}} {{i18n "admin.dashboard.uploads"}}</h3>
<p>
{{diskSpace.uploads_used}} ({{i18n "admin.dashboard.space_free" size=diskSpace.uploads_free}})
</p>
</div>
</div>
{{/if}}

<div class="last-dashboard-update">
<div>
<div class="misc">
{{admin-report
forcedModes="storage-stats"
dataSourceName="storage_stats"
showHeader=false}}

<div class="last-dashboard-update">
<div>
<h4>{{i18n "admin.dashboard.last_updated"}} </h4>
<p>{{updatedTimestamp}}</p>
<a rel="noopener" target="_blank" href="https://meta.discourse.org/tags/release-notes" class="btn btn-default">
{{i18n "admin.dashboard.whats_new_in_discourse"}}
</a>
</div>
<a rel="noopener" target="_blank" href="https://meta.discourse.org/tags/release-notes" class="btn btn-default">
{{i18n "admin.dashboard.whats_new_in_discourse"}}
</a>
</div>
</div>
</div>

<p>
{{i18n 'admin.dashboard.find_old'}} {{#link-to 'admin.dashboard'}}{{i18n "admin.dashboard.old_link"}}{{/link-to}}
</p>
{{/conditional-loading-section}}
<p>
{{i18n 'admin.dashboard.find_old'}} {{#link-to 'admin.dashboard'}}{{i18n "admin.dashboard.old_link"}}{{/link-to}}
</p>
</div>

<div class="section-column">
Expand Down
10 changes: 3 additions & 7 deletions app/assets/stylesheets/common/admin/dashboard_next.scss
Expand Up @@ -191,15 +191,15 @@
display: flex;
border: 1px solid $primary-low;

.durability,
.storage-stats,
.last-dashboard-update {
flex: 1 1 50%;
box-sizing: border-box;
margin: 1em 0;
padding: 0 1em;
}

.durability {
.storage-stats {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
Expand All @@ -213,15 +213,11 @@
.uploads p:last-of-type {
margin-bottom: 0;
}

.durability-title {
text-transform: capitalize;
}
}

@media screen and (max-width: 400px) {
flex-wrap: wrap;
.durability,
.storage-stats,
.last-dashboard-update {
flex: 1 1 100%;
text-align: left;
Expand Down
2 changes: 0 additions & 2 deletions app/controllers/admin/dashboard_controller.rb
@@ -1,9 +1,7 @@
require 'disk_space'
class Admin::DashboardController < Admin::AdminController
def index
dashboard_data = AdminDashboardData.fetch_cached_stats || Jobs::DashboardStats.new.execute({})
dashboard_data.merge!(version_check: DiscourseUpdates.check_version.as_json) if SiteSetting.version_checks?
dashboard_data[:disk_space] = DiskSpace.cached_stats
render json: dashboard_data
end

Expand Down
23 changes: 1 addition & 22 deletions app/controllers/admin/dashboard_next_controller.rb
@@ -1,5 +1,3 @@
require 'disk_space'

class Admin::DashboardNextController < Admin::AdminController
def index
data = AdminDashboardNextIndexData.fetch_cached_stats
Expand All @@ -15,25 +13,6 @@ def moderation; end
def security; end

def general
data = AdminDashboardNextGeneralData.fetch_cached_stats

if SiteSetting.enable_backups
data[:last_backup_taken_at] = last_backup_taken_at
data[:disk_space] = DiskSpace.cached_stats
end

render json: data
end

private

def last_backup_taken_at
store = BackupRestore::BackupStore.create

begin
store.latest_file&.last_modified
rescue BackupRestore::BackupStore::StorageError
nil
end
render json: AdminDashboardNextGeneralData.fetch_cached_stats
end
end
12 changes: 0 additions & 12 deletions app/jobs/regular/update_disk_space.rb

This file was deleted.

34 changes: 29 additions & 5 deletions app/models/report.rb
Expand Up @@ -49,8 +49,10 @@ def self.cache_key(report)
].compact.map(&:to_s).join(':')
end

def self.clear_cache
Discourse.cache.keys("reports:*").each do |key|
def self.clear_cache(type = nil)
pattern = type ? "reports:#{type}:*" : "reports:*"

Discourse.cache.keys(pattern).each do |key|
Discourse.cache.redis.del(key)
end
end
Expand All @@ -76,9 +78,9 @@ def as_json(options = nil)

{
type: type,
title: I18n.t("reports.#{type}.title"),
xaxis: I18n.t("reports.#{type}.xaxis"),
yaxis: I18n.t("reports.#{type}.yaxis"),
title: I18n.t("reports.#{type}.title", default: nil),
xaxis: I18n.t("reports.#{type}.xaxis", default: nil),
yaxis: I18n.t("reports.#{type}.yaxis", default: nil),
description: description.presence ? description : nil,
data: data,
start_date: start_date&.iso8601,
Expand Down Expand Up @@ -1407,6 +1409,28 @@ def self.report_suspicious_logins(report)
end
end

def self.report_storage_stats(report)
backup_stats = begin
BackupRestore::BackupStore.create.stats
rescue BackupRestore::BackupStore::StorageError
nil
end

report.data = {
backups: backup_stats,
uploads: {
used_bytes: DiskSpace.uploads_used_bytes,
free_bytes: DiskSpace.uploads_free_bytes
}
}
end

DiscourseEvent.on(:site_setting_saved) do |site_setting|
if ["backup_location", "s3_backup_bucket"].include?(site_setting.name.to_s)
clear_cache(:storage_stats)
end
end

private

def hex_to_rgbs(hex_color)
Expand Down
12 changes: 8 additions & 4 deletions config/locales/client.en.yml
Expand Up @@ -2839,9 +2839,13 @@ en:
private_messages_short: "Msgs"
private_messages_title: "Messages"
mobile_title: "Mobile"
space_free: "{{size}} free"
uploads: "uploads"
backups: "backups"
space_used: "%{usedSize} used"
space_used_and_free: "%{usedSize} (%{freeSize} free)"
uploads: "Uploads"
backups: "Backups"
backup_count:
one: "%{count} backup on %{location}"
other: "%{count} backups on %{location}"
lastest_backup: "Latest: %{date}"
traffic_short: "Traffic"
traffic: "Application web requests"
Expand Down Expand Up @@ -3216,7 +3220,7 @@ en:
title: "Rollback the database to previous working state"
confirm: "Are you sure you want to rollback the database to the previous working state?"
location:
local: "Local"
local: "Local Storage"
s3: "Amazon S3"

export_csv:
Expand Down

2 comments on commit 1a8ca68

@discoursebot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/dashboard-fails-due-to-s3-timeout/103687/9

@jjaffeux
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Please sign in to comment.