+
+
+ <:breadcrumbs>
+
+
+ <:actions as |actions|>
+
+
+ <:tabs>
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/addon/templates/dashboard.hbs b/app/assets/javascripts/admin/addon/templates/dashboard.hbs
index 906d43ae065..2d0e81ff674 100644
--- a/app/assets/javascripts/admin/addon/templates/dashboard.hbs
+++ b/app/assets/javascripts/admin/addon/templates/dashboard.hbs
@@ -2,6 +2,12 @@
+
+ <:breadcrumbs>
+
+
+
+
{{#if this.showVersionChecks}}
diff --git a/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs b/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs
index 3c6fcbfa7de..8767620bcbe 100644
--- a/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs
+++ b/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs
@@ -6,7 +6,7 @@
-
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index 51416738695..604425c11ac 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -1077,6 +1077,7 @@ a.inline-editable-field {
@import "common/admin/admin_report_stacked_line_chart";
@import "common/admin/admin_report_table";
@import "common/admin/admin_report_inline_table";
+@import "common/admin/admin_page_header";
@import "common/admin/admin_intro";
@import "common/admin/admin_emojis";
@import "common/admin/mini_profiler";
diff --git a/app/assets/stylesheets/common/admin/admin_config_area.scss b/app/assets/stylesheets/common/admin/admin_config_area.scss
index b9046069b30..69372bf528f 100644
--- a/app/assets/stylesheets/common/admin/admin_config_area.scss
+++ b/app/assets/stylesheets/common/admin/admin_config_area.scss
@@ -35,3 +35,31 @@
}
}
}
+
+.admin-config-page {
+ &__main-area {
+ .admin-detail {
+ padding-top: 15px;
+ border-left: 0;
+ padding-left: 0;
+ width: 100%;
+ }
+ }
+}
+
+.admin-config-area {
+ &__settings {
+ .admin-site-settings-filter-controls {
+ margin-bottom: 1em;
+ }
+
+ .setting-label {
+ margin-left: 18px;
+ }
+ }
+
+ &__empty-list {
+ padding: 1em;
+ border: 1px solid var(--primary-low);
+ }
+}
diff --git a/app/assets/stylesheets/common/admin/admin_page_header.scss b/app/assets/stylesheets/common/admin/admin_page_header.scss
new file mode 100644
index 00000000000..fa8a858e461
--- /dev/null
+++ b/app/assets/stylesheets/common/admin/admin_page_header.scss
@@ -0,0 +1,44 @@
+.admin-page-header,
+.admin-page-subheader {
+ &__title-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1em;
+
+ h1,
+ h3 {
+ margin: 0;
+ }
+
+ .admin-page-header__actions {
+ display: flex;
+ align-items: center;
+ button {
+ margin-left: 1em;
+ }
+ }
+ }
+
+ .admin-nav-submenu {
+ background: transparent;
+ border-bottom: 1px solid var(--primary-low);
+
+ .horizontal-overflow-nav {
+ background: transparent;
+
+ &:before {
+ display: none;
+ }
+
+ &:after {
+ display: none;
+ }
+ }
+
+ .nav-pills {
+ width: auto;
+ margin: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/admin/backups.scss b/app/assets/stylesheets/common/admin/backups.scss
index 68e97b260f2..cf276c14c88 100644
--- a/app/assets/stylesheets/common/admin/backups.scss
+++ b/app/assets/stylesheets/common/admin/backups.scss
@@ -1,29 +1,10 @@
// Styles for /admin/backups
-$rollback: #3d9970;
-$rollback-dark: darken($rollback, 10%) !default;
-$rollback-darker: darken($rollback, 20%) !default;
-.btn-rollback {
- color: var(--secondary);
- background: $rollback;
- .d-icon {
- color: var(--secondary);
- }
- &:hover {
- background: $rollback-dark;
- .d-icon {
- color: currentColor;
- }
- }
- &:active {
- @include linear-gradient($rollback-darker, $rollback-dark);
- }
- &[disabled] {
- background: $rollback;
- }
-}
-
.admin-backups {
+ .before-backup-list-outlet {
+ margin-top: 1em;
+ }
+
table {
@media screen and (min-width: 550px) {
td.backup-filename {
@@ -71,43 +52,20 @@ $rollback-darker: darken($rollback, 20%) !default;
overflow: auto;
}
-button.ru {
- position: relative;
- min-width: 110px;
-}
-
-.ru-progress {
- position: absolute;
- top: 0;
- left: 0;
- height: 100%;
- background: rgba(0, 175, 0, 0.3);
-}
-
-.is-uploading:hover .ru-progress {
- background: rgba(200, 0, 0, 0.3);
-}
-
.start-backup-modal {
.alert {
margin-bottom: 0;
}
}
-.backup-options {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- .btn {
- margin-right: 0.5em;
- }
- .backup-message {
- margin-left: auto;
- @include breakpoint(mobile-extra-large) {
- margin: 1.25em 0 0;
- }
- }
- label {
- font-weight: normal;
+.backup-message {
+ margin-left: auto;
+ margin-top: 1em;
+ @include breakpoint(mobile-extra-large) {
+ margin: 1.25em 0 0;
}
}
+
+label.admin-backups-upload {
+ font-weight: 400;
+}
diff --git a/app/assets/stylesheets/common/admin/dashboard.scss b/app/assets/stylesheets/common/admin/dashboard.scss
index 75fd827fc7b..2971d24bf25 100644
--- a/app/assets/stylesheets/common/admin/dashboard.scss
+++ b/app/assets/stylesheets/common/admin/dashboard.scss
@@ -1,8 +1,6 @@
.admin-reports,
.dashboard-next {
&.admin-contents {
- margin: 10px 0 0 0;
-
nav {
position: relative;
width: calc(100% + 10px);
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index cccdd1d6824..404cd3d2a6f 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -295,6 +295,7 @@ en:
now: "just now"
read_more: "read more"
more: "More"
+ more_options: "More options"
x_more:
one: "%{count} More"
other: "%{count} More"
@@ -2219,6 +2220,7 @@ en:
}.
learn_more: "Learn more…"
+ learn_more_with_link: "Learn more…"
mute: Mute
unmute: Unmute
@@ -5012,6 +5014,7 @@ en:
admin_js:
# This is a text input placeholder, keep the translation short
type_to_filter: "type to filter…"
+ settings: "Settings"
admin:
title: "Discourse Admin"
@@ -5658,8 +5661,12 @@ en:
backups:
title: "Backups"
+ files_title: "Backup files"
+ description: "Discourse backups include the full site database, which contains everything on the site: topics, posts, users, groups, settings, themes, etc. Depending on how the backup file is created, it may or may not include uploads."
+ learn_more_url: ""
menu:
backups: "Backups"
+ backup_files: "Backup files"
logs: "Logs"
none: "No backup available."
read_only:
diff --git a/config/routes.rb b/config/routes.rb
index f20a2394957..7b0c25b3049 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -365,6 +365,7 @@ Discourse::Application.routes.draw do
:format => :json
get "logs" => "backups#logs"
+ get "settings" => "backups#index"
get "status" => "backups#status"
delete "cancel" => "backups#cancel"
post "rollback" => "backups#rollback"
diff --git a/spec/system/admin_backups_spec.rb b/spec/system/admin_backups_spec.rb
new file mode 100644
index 00000000000..d110bc7188c
--- /dev/null
+++ b/spec/system/admin_backups_spec.rb
@@ -0,0 +1,83 @@
+#frozen_string_literal: true
+
+describe "Admin Backups Page", type: :system do
+ fab!(:current_user) { Fabricate(:admin) }
+ let(:backups_page) { PageObjects::Pages::AdminBackups.new }
+ let(:dialog) { PageObjects::Components::Dialog.new }
+ let(:settings_page) { PageObjects::Pages::AdminSiteSettings.new }
+
+ let(:root_directory) { setup_local_backups }
+
+ def create_backups
+ create_local_backup_file(
+ root_directory: root_directory,
+ db_name: "default",
+ filename: "b.tar.gz",
+ last_modified: "2024-07-13T15:10:00Z",
+ size_in_bytes: 10,
+ )
+ create_local_backup_file(
+ root_directory: root_directory,
+ db_name: "default",
+ filename: "old.tar.gz",
+ last_modified: "2024-06-01T13:10:00Z",
+ size_in_bytes: 5,
+ )
+ end
+
+ before do
+ sign_in(current_user)
+ create_backups
+ BackupRestore::LocalBackupStore.stubs(:base_directory).returns(
+ root_directory + "/" + RailsMultisite::ConnectionManagement.current_db,
+ )
+ end
+ after { teardown_local_backups(root_directory: root_directory) }
+
+ it "shows a list of backups" do
+ backups_page.visit_page
+ expect(backups_page).to have_backup_listed("b.tar.gz")
+ expect(backups_page).to have_backup_listed("old.tar.gz")
+ end
+
+ it "can download a backup, which sends an email" do
+ backups_page.visit_page
+ backups_page.download_backup("b.tar.gz")
+ expect(page).to have_content(I18n.t("admin_js.admin.backups.operations.download.alert"))
+ expect_job_enqueued(
+ job: :download_backup_email,
+ args: {
+ user_id: current_user.id,
+ backup_file_path: Discourse.base_url + "/admin/backups/b.tar.gz",
+ },
+ )
+ end
+
+ it "can delete a backup" do
+ backups_page.visit_page
+ backups_page.delete_backup("b.tar.gz")
+ dialog.click_yes
+ expect(backups_page).to have_no_backup_listed("b.tar.gz")
+ end
+
+ it "can restore a backup" do
+ backups_page.visit_page
+ backups_page.expand_backup_row_menu("b.tar.gz")
+ expect(backups_page).to have_css(backups_page.row_button_selector("restore"))
+ end
+
+ it "can toggle read-only mode" do
+ backups_page.visit_page
+ backups_page.toggle_read_only
+ dialog.click_yes
+ expect(page).to have_content(I18n.t("js.read_only_mode.enabled"))
+ backups_page.toggle_read_only
+ expect(page).to have_no_content(I18n.t("js.read_only_mode.enabled"))
+ end
+
+ it "can see backup site settings" do
+ backups_page.visit_page
+ backups_page.click_tab("settings")
+ expect(settings_page).to have_setting("enable_backups")
+ end
+end
diff --git a/spec/system/page_objects/admin_backups.rb b/spec/system/page_objects/admin_backups.rb
new file mode 100644
index 00000000000..f6ad4d423c5
--- /dev/null
+++ b/spec/system/page_objects/admin_backups.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module PageObjects
+ module Pages
+ class AdminBackups < PageObjects::Pages::Base
+ def visit_page
+ page.visit "/admin/backups"
+ self
+ end
+
+ def click_tab(tab_name)
+ case tab_name
+ when "settings"
+ find(".admin-backups-tabs__settings").click
+ when "files"
+ find(".admin-backups-tabs__files").click
+ when "logs"
+ find(".admin-backups-tabs__logs").click
+ end
+ end
+
+ def has_backup_listed?(filename)
+ page.has_css?(backup_row_selector(filename))
+ end
+
+ def has_no_backup_listed?(filename)
+ page.has_no_css?(backup_row_selector(filename))
+ end
+
+ def open_upload_backup_modal
+ find(".admin-backups__start").click
+ end
+
+ def download_backup(filename)
+ find_backup_row(filename).find(row_button_selector("download")).click
+ end
+
+ def expand_backup_row_menu(filename)
+ find_backup_row(filename).find(".backup-item-menu-trigger").click
+ end
+
+ def delete_backup(filename)
+ expand_backup_row_menu(filename)
+ find(".backup-item-menu-content").find(row_button_selector("delete")).click
+ end
+
+ def restore_backup(filename)
+ expand_backup_row_menu(filename)
+ find(".backup-item-menu-content").find(row_button_selector("restore")).click
+ end
+
+ def find_backup_row(filename)
+ find(backup_row_selector(filename))
+ end
+
+ def backup_row_selector(filename)
+ ".admin-backups-list .backup-item-row[data-backup-filename='#{filename}']"
+ end
+
+ def row_button_selector(button_name)
+ ".backup-item-row__#{button_name}"
+ end
+
+ def toggle_read_only
+ find(".admin-backups__toggle-read-only").click
+ end
+ end
+ end
+end
diff --git a/spec/system/page_objects/pages/admin_site_settings.rb b/spec/system/page_objects/pages/admin_site_settings.rb
index 09d92c3d59a..480965e5a25 100644
--- a/spec/system/page_objects/pages/admin_site_settings.rb
+++ b/spec/system/page_objects/pages/admin_site_settings.rb
@@ -22,9 +22,17 @@ module PageObjects
self
end
+ def setting_row_selector(setting_name)
+ ".row.setting[data-setting='#{setting_name}']"
+ end
+
+ def has_setting?(setting_name)
+ has_css?(".row.setting[data-setting=\"#{setting_name}\"]")
+ end
+
def find_setting(setting_name, overridden: false)
find(
- ".admin-detail .row.setting[data-setting='#{setting_name}']#{overridden ? ".overridden" : ""}",
+ ".admin-detail #{setting_row_selector(setting_name)}#{overridden ? ".overridden" : ""}",
)
end