readonly mode

This commit is contained in:
Régis Hanol 2014-02-12 20:37:28 -08:00
parent faf03fdeb1
commit e7472dc374
6 changed files with 84 additions and 25 deletions

View File

@ -8,8 +8,9 @@ Discourse.addInitializer(function() {
Discourse.MessageBus.alwaysLongPoll = Discourse.Environment === "development"; Discourse.MessageBus.alwaysLongPoll = Discourse.Environment === "development";
Discourse.MessageBus.start(); Discourse.MessageBus.start();
Discourse.MessageBus.subscribe("/global/asset-version", function(version){ Discourse.MessageBus.subscribe("/global/asset-version", function(version){
Discourse.set("assetVersion",version); Discourse.set("assetVersion", version);
if(Discourse.get("requiresRefresh")) { if(Discourse.get("requiresRefresh")) {
// since we can do this transparently for people browsing the forum // since we can do this transparently for people browsing the forum
@ -24,5 +25,15 @@ Discourse.addInitializer(function() {
} }
}); });
Discourse.set("isReadOnly", Discourse.Site.currentProp("is_readonly"));
Discourse.MessageBus.subscribe("/global/read-only", function (enabled) {
Discourse.set("isReadOnly", enabled);
if (enabled) {
bootbox.alert(I18n.t("read_only_mode_enabled"));
}
});
Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus); Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
}, true); }, true);

View File

@ -29,7 +29,7 @@ class ApplicationController < ActionController::Base
before_filter :set_mobile_view before_filter :set_mobile_view
before_filter :inject_preview_style before_filter :inject_preview_style
before_filter :disable_customization before_filter :disable_customization
before_filter :block_if_maintenance_mode before_filter :block_if_readonly_mode
before_filter :authorize_mini_profiler before_filter :authorize_mini_profiler
before_filter :store_incoming_links before_filter :store_incoming_links
before_filter :preload_json before_filter :preload_json
@ -50,7 +50,6 @@ class ApplicationController < ActionController::Base
raise raise
end end
# Some exceptions # Some exceptions
class RenderEmpty < Exception; end class RenderEmpty < Exception; end
@ -87,6 +86,11 @@ class ApplicationController < ActionController::Base
rescue_discourse_actions("[error: 'invalid access']", 403) # TODO: this breaks json responses rescue_discourse_actions("[error: 'invalid access']", 403) # TODO: this breaks json responses
end end
rescue_from Discourse::ReadOnly do
# can this happen on a not .json format?
render json: failed_json.merge(message: I18n.t("read_only_mode_enabled"))
end
def rescue_discourse_actions(message, error) def rescue_discourse_actions(message, error)
if request.format && request.format.json? if request.format && request.format.json?
# TODO: this doesn't make sense. Stuffing an html page into a json response will cause # TODO: this doesn't make sense. Stuffing an html page into a json response will cause
@ -249,16 +253,6 @@ class ApplicationController < ActionController::Base
end end
end end
def block_if_maintenance_mode
if Discourse.maintenance_mode?
if request.format.json?
render status: 503, json: failed_json.merge(message: I18n.t('site_under_maintenance'))
else
render status: 503, file: File.join( Rails.root, 'public', '503.html' ), layout: false
end
end
end
def mini_profiler_enabled? def mini_profiler_enabled?
defined?(Rack::MiniProfiler) && current_user.try(:admin?) defined?(Rack::MiniProfiler) && current_user.try(:admin?)
end end
@ -288,6 +282,11 @@ class ApplicationController < ActionController::Base
redirect_to :login if SiteSetting.login_required? redirect_to :login if SiteSetting.login_required?
end end
def block_if_readonly_mode
return if request.put? && request.fullpath == "/admin/backups/readonly"
raise Discourse::ReadOnly.new unless request.get? || !Discourse.readonly_mode?
end
def build_not_found_page(status=404, layout=false) def build_not_found_page(status=404, layout=false)
@top_viewed = Topic.top_viewed(10) @top_viewed = Topic.top_viewed(10)
@recent = Topic.recent(10) @recent = Topic.recent(10)

View File

@ -8,7 +8,8 @@ class SiteSerializer < ApplicationSerializer
:periods, :periods,
:top_menu_items, :top_menu_items,
:anonymous_top_menu_items, :anonymous_top_menu_items,
:uncategorized_category_id # this is hidden so putting it here :uncategorized_category_id, # this is hidden so putting it here
:is_readonly
has_many :categories, serializer: BasicCategorySerializer, embed: :objects has_many :categories, serializer: BasicCategorySerializer, embed: :objects
has_many :post_action_types, embed: :objects has_many :post_action_types, embed: :objects
@ -45,4 +46,8 @@ class SiteSerializer < ApplicationSerializer
SiteSetting.uncategorized_category_id SiteSetting.uncategorized_category_id
end end
def is_readonly
Discourse.readonly_mode?
end
end end

View File

@ -409,7 +409,9 @@ uncategorized:
summary_likes_required: 1 summary_likes_required: 1
summary_percent_filter: 20 summary_percent_filter: 20
send_welcome_message: true send_welcome_message: true
allow_import: false allow_import:
client: true
default: false
educate_until_posts: educate_until_posts:
client: true client: true
default: 2 default: 2

View File

@ -27,6 +27,9 @@ module Discourse
class InvalidPost < Exception; end class InvalidPost < Exception; end
# When read-only mode is enabled
class ReadOnly < Exception; end
# Cross site request forgery # Cross site request forgery
class CSRF < Exception; end class CSRF < Exception; end
@ -138,18 +141,20 @@ module Discourse
return base_url_no_prefix + base_uri return base_url_no_prefix + base_uri
end end
def self.enable_maintenance_mode def self.enable_readonly_mode
$redis.set maintenance_mode_key, 1 $redis.set readonly_mode_key, 1
MessageBus.publish(readonly_channel, true)
true true
end end
def self.disable_maintenance_mode def self.disable_readonly_mode
$redis.del maintenance_mode_key $redis.del readonly_mode_key
MessageBus.publish(readonly_channel, false)
true true
end end
def self.maintenance_mode? def self.readonly_mode?
!!$redis.get( maintenance_mode_key ) !!$redis.get(readonly_mode_key)
end end
def self.git_version def self.git_version
@ -198,9 +203,12 @@ module Discourse
Rails.configuration.action_controller.asset_host Rails.configuration.action_controller.asset_host
end end
private def self.readonly_mode_key
"readonly_mode"
def self.maintenance_mode_key
'maintenance_mode'
end end
def self.readonly_channel
"/global/read-only"
end
end end

View File

@ -82,5 +82,39 @@ describe Discourse do
end end
context "#enable_readonly_mode" do
it "adds a key in redis and publish a message through the message bus" do
$redis.expects(:set).with(Discourse.readonly_mode_key, 1)
MessageBus.expects(:publish).with(Discourse.readonly_channel, true)
Discourse.enable_readonly_mode
end
end
context "#disable_readonly_mode" do
it "removes a key from redis and publish a message through the message bus" do
$redis.expects(:del).with(Discourse.readonly_mode_key)
MessageBus.expects(:publish).with(Discourse.readonly_channel, false)
Discourse.disable_readonly_mode
end
end
context "#readonly_mode?" do
it "returns true when the key is present in redis" do
$redis.expects(:get).with(Discourse.readonly_mode_key).returns("1")
Discourse.readonly_mode?.should == true
end
it "returns false when the key is not present in redis" do
$redis.expects(:get).with(Discourse.readonly_mode_key).returns(nil)
Discourse.readonly_mode?.should == false
end
end
end end