Merge branch 'master' into fix-by-external

Conflicts:
	app/controllers/users_controller.rb
This commit is contained in:
Ryan Fox 2015-05-15 19:54:11 -04:00
commit 14d2b76354
2109 changed files with 125991 additions and 188642 deletions

View File

@ -12,6 +12,7 @@ lib/javascripts/locale/
lib/javascripts/messageformat.js
lib/javascripts/moment.js
lib/javascripts/moment_locale/
lib/highlight_js/
lib/es6_module_transpiler/support/es6-module-transpiler.js
public/javascripts/
spec/phantom_js/smoke_test.js
@ -19,4 +20,5 @@ vendor/
test/javascripts/helpers/
test/javascripts/test_helper.js
test/javascripts/test_helper.js
app/assets/javascripts/ember-addons/

View File

@ -4,7 +4,6 @@
"$",
"RSVP",
"Discourse",
"$LAB",
"Em",
"PreloadStore",
"Handlebars",
@ -15,7 +14,6 @@
"moduleForComponent",
"Pretender",
"sandbox",
"integration",
"controllerFor",
"test",
"ok",
@ -35,6 +33,10 @@
"triggerEvent",
"count",
"exists",
"visible",
"invisible",
"asyncRender",
"selectDropdown",
"asyncTestDiscourse",
"fixture",
"find",
@ -47,6 +49,7 @@
"parseHTML",
"deepEqual",
"notEqual",
"define",
"require",
"requirejs",
"hasModule",

View File

@ -5,20 +5,25 @@ env:
- DISCOURSE_HOSTNAME=www.example.com
- RUBY_GC_MALLOC_LIMIT=50000000
matrix:
- "RAILS42=1"
- "RAILS_MASTER=1"
- "RAILS_MASTER=0"
addons:
postgresql: 9.3
matrix:
allow_failures:
- rvm: 2.0.0
env: "RAILS_MASTER=1"
- rvm: 2.1
env: "RAILS_MASTER=1"
- env: "RAILS_MASTER=1"
- env: "RAILS42=1"
- rvm: rbx-2
fast_finish: true
rvm:
- 2.0.0
- 2.1
- 2.2
- rbx-2
services:
- redis-server
@ -32,9 +37,11 @@ before_install:
- jshint .
before_script:
- psql -c 'create database discourse_test;' -U postgres
- bundle exec rake db:migrate
- bundle exec rake db:create db:migrate
bundler_args: --without development --deployment --retry=3 --jobs=3
install:
- bash -c "if [ '$RAILS42' == '1' ]; then bundle update --retry=3 --jobs=3 rails rails-observers; fi"
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails rails-observers seed-fu; fi"
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
script: 'bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test'

View File

@ -1,92 +1,57 @@
[main]
host = https://www.transifex.com
lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt
[discourse-org.clientenyml]
file_filter = config/locales/client.<lang>.yml
source_file = config/locales/client.en.yml
source_lang = en
trans.es_ES = config/locales/client.es.yml
trans.fr_FR = config/locales/client.fr.yml
trans.ko_KR = config/locales/client.ko.yml
trans.pt_PT = config/locales/client.pt.yml
type = YML
[discourse-org.serverenyml]
file_filter = config/locales/server.<lang>.yml
source_file = config/locales/server.en.yml
source_lang = en
trans.es_ES = config/locales/server.es.yml
trans.fr_FR = config/locales/server.fr.yml
trans.ko_KR = config/locales/server.ko.yml
trans.pt_PT = config/locales/server.pt.yml
type = YML
[discourse-org.pollclientenyml]
file_filter = plugins/poll/config/locales/client.<lang>.yml
source_file = plugins/poll/config/locales/client.en.yml
source_lang = en
trans.es_ES = plugins/poll/config/locales/client.es.yml
trans.fr_FR = plugins/poll/config/locales/client.fr.yml
trans.ko_KR = plugins/poll/config/locales/client.ko.yml
trans.pt_PT = plugins/poll/config/locales/client.pt.yml
type = YML
[discourse-org.pollserverenyml]
file_filter = plugins/poll/config/locales/server.<lang>.yml
source_file = plugins/poll/config/locales/server.en.yml
source_lang = en
trans.es_ES = plugins/poll/config/locales/server.es.yml
trans.fr_FR = plugins/poll/config/locales/server.fr.yml
trans.ko_KR = plugins/poll/config/locales/server.ko.yml
trans.pt_PT = plugins/poll/config/locales/server.pt.yml
type = YML
[discourse-org.imgurserverenyml]
file_filter = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.<lang>.yml
source_file = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.en.yml
source_lang = en
trans.es_ES = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.es.yml
trans.fr_FR = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.fr.yml
trans.ko_KR = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.ko.yml
trans.pt_PT = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.pt.yml
type = YML
[discourse-org.403html]
file_filter = public/403.<lang>.html
source_file = public/403.html
source_lang = en
trans.es_ES = public/403.es.html
trans.fr_FR = public/403.fr.html
trans.ko_KR = public/403.ko.html
trans.pt_PT = public/403.pt.html
type = HTML
[discourse-org.422html]
file_filter = public/422.<lang>.html
source_file = public/422.html
source_lang = en
trans.es_ES = public/422.es.html
trans.fr_FR = public/422.fr.html
trans.ko_KR = public/422.ko.html
trans.pt_PT = public/422.pt.html
type = HTML
[discourse-org.500html]
file_filter = public/500.<lang>.html
source_file = public/500.html
source_lang = en
trans.es_ES = public/500.es.html
trans.fr_FR = public/500.fr.html
trans.ko_KR = public/500.ko.html
trans.pt_PT = public/500.pt.html
type = HTML
[discourse-org.503html]
file_filter = public/503.<lang>.html
source_file = public/503.html
source_lang = en
trans.es_ES = public/503.es.html
trans.fr_FR = public/503.fr.html
trans.ko_KR = public/503.ko.html
trans.pt_PT = public/503.pt.html
type = HTML

119
Gemfile
View File

@ -2,81 +2,27 @@ source 'https://rubygems.org'
# if there is a super emergency and rubygems is playing up, try
#source 'http://production.cf.rubygems.org'
module ::Kernel
def rails_master?
ENV["RAILS_MASTER"] == '1'
end
end
if rails_master?
# monkey patching to support dual booting
module Bundler::SharedHelpers
def default_lockfile=(path)
@default_lockfile = path
def rails_42?
ENV["RAILS42"] == '1'
end
def default_lockfile
@default_lockfile ||= Pathname.new("#{default_gemfile}.lock")
end
end
Bundler::SharedHelpers.default_lockfile = Pathname.new("#{Bundler::SharedHelpers.default_gemfile}_master.lock")
# Bundler::Dsl.evaluate already called with an incorrect lockfile ... fix it
class Bundler::Dsl
# A bit messy, this can be called multiple times by bundler, avoid blowing the stack
unless self.method_defined? :to_definition_unpatched
alias_method :to_definition_unpatched, :to_definition
end
def to_definition(bad_lockfile, unlock)
to_definition_unpatched(Bundler::SharedHelpers.default_lockfile, unlock)
end
end
end
# Monkey patch bundler to support mri_21
unless Bundler::Dependency::PLATFORM_MAP.include? :mri_21
STDERR.puts
STDERR.puts "WARNING: --------------------------------------------------------------------------"
STDERR.puts "You are running an old version of bundler, please update by running: gem install bundler"
STDERR.puts
map = Bundler::Dependency::PLATFORM_MAP.dup
map[:mri_21] = Gem::Platform::RUBY
map.freeze
Bundler::Dependency.send(:remove_const, "PLATFORM_MAP")
Bundler::Dependency.const_set("PLATFORM_MAP", map)
Bundler::Dsl.send(:remove_const, "VALID_PLATFORMS")
Bundler::Dsl.const_set("VALID_PLATFORMS", map.keys.freeze)
class ::Bundler::CurrentRuby
def on_21?
RUBY_VERSION =~ /^2\.1/
end
def mri_21?
mri? && on_21?
end
end
class ::Bundler::Dependency
private
def on_21?
RUBY_VERSION =~ /^2\.1/
end
def mri_21?
mri? && on_21?
end
end
end
if rails_master?
gem 'arel', git: 'https://github.com/rails/arel.git'
gem 'rails', git: 'https://github.com/rails/rails.git'
gem 'rails-observers', git: 'https://github.com/SamSaffron/rails-observers.git'
gem 'rails-observers', git: 'https://github.com/rails/rails-observers.git'
gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse'
elsif rails_42?
gem 'rails', '~> 4.2.1'
gem 'rails-observers', git: 'https://github.com/rails/rails-observers.git'
gem 'seed-fu', '~> 2.3.5'
else
gem 'seed-fu', '~> 2.3.3'
gem 'rails'
gem 'rails', '~> 4.1.10'
gem 'rails-observers'
gem 'seed-fu', '~> 2.3.3'
end
gem 'actionpack-action_caching'
@ -91,22 +37,15 @@ gem 'mail', '~> 2.5.4'
gem 'hiredis'
gem 'redis', require: ["redis", "redis/connection/hiredis"]
# We use some ams 0.8.0 features, need to amend code
# to support 0.9 etc, bench needs to run and ensure no
# perf regressions
if rails_master?
gem 'active_model_serializers', github: 'rails-api/active_model_serializers', branch: '0-8-stable'
else
gem 'active_model_serializers', '~> 0.8.0'
end
gem 'active_model_serializers', '~> 0.8.3'
gem 'onebox'
gem 'ember-rails'
gem 'ember-source', '1.9.0.beta.4'
gem 'ember-source', '1.11.3.1'
gem 'handlebars-source', '2.0.0'
gem 'barber'
gem 'babel-transpiler'
gem 'message_bus'
gem 'rails_multisite', path: 'vendor/gems/rails_multisite'
@ -116,7 +55,9 @@ gem 'eventmachine'
gem 'fast_xs'
gem 'fast_xor'
gem 'fastimage'
# while we sort out https://github.com/sdsykes/fastimage/pull/46
gem 'fastimage_discourse', require: 'fastimage'
gem 'fog', '1.26.0', require: false
gem 'unf', require: false
@ -144,6 +85,7 @@ gem 'omniauth-google-oauth2'
gem 'oj'
gem 'pg'
gem 'pry-rails', require: false
gem 'r2', '~> 0.2.5', require: false
gem 'rake'
@ -165,14 +107,7 @@ gem 'rack-protection' # security
# in production environments by default.
# allow everywhere for now cause we are allowing asset debugging in prd
group :assets do
if rails_master?
gem 'sass-rails', git: 'https://github.com/rails/sass-rails.git'
else
# later is breaking our asset compliation extensions
gem 'sass-rails', '4.0.2'
end
gem 'sass-rails', '~> 4.0.5'
gem 'uglifier'
gem 'rtlit', require: false # for css rtling
end
@ -183,14 +118,13 @@ group :test do
end
group :test, :development do
# while upgrading to 3
gem 'rspec', '2.99.0'
gem 'rspec', '~> 3.2.0'
gem 'mock_redis'
gem 'listen', '0.7.3', require: false
gem 'certified', require: false
# later appears to break Fabricate(:topic, category: category)
gem 'fabrication', '2.9.8', require: false
gem 'qunit-rails'
gem 'discourse-qunit-rails', require: 'qunit-rails'
gem 'mocha', require: false
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false
@ -238,16 +172,19 @@ gem 'ruby-readability', require: false
gem 'simple-rss', require: false
# TODO mri_22 should be here, but bundler was real slow to pick it up
# not even in production bundler yet, monkey patching it in feels bad
gem 'gctools', require: false, platform: :mri_21
gem 'stackprof', require: false, platform: :mri_21
gem 'memory_profiler', require: false, platform: :mri_21
begin
gem 'stackprof', require: false, platform: [:mri_21, :mri_22]
gem 'memory_profiler', require: false, platform: [:mri_21, :mri_22]
rescue Bundler::GemfileError
STDERR.puts "You are running an old version of bundler, please upgrade bundler ASAP, if you are using Discourse docker, rebuild your container."
gem 'stackprof', require: false, platform: [:mri_21]
gem 'memory_profiler', require: false, platform: [:mri_21]
end
gem 'rmmseg-cpp', require: false
gem 'stringex', require: false
gem 'logster'
# perftools only works on 1.9 atm

View File

@ -7,87 +7,89 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.2.8)
actionmailer (4.1.8)
actionpack (= 4.1.8)
actionview (= 4.1.8)
actionmailer (4.1.10)
actionpack (= 4.1.10)
actionview (= 4.1.10)
mail (~> 2.5, >= 2.5.4)
actionpack (4.1.8)
actionview (= 4.1.8)
activesupport (= 4.1.8)
actionpack (4.1.10)
actionview (= 4.1.10)
activesupport (= 4.1.10)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
actionpack-action_caching (1.1.1)
actionpack (>= 4.0.0, < 5.0)
actionview (4.1.8)
activesupport (= 4.1.8)
actionview (4.1.10)
activesupport (= 4.1.10)
builder (~> 3.1)
erubis (~> 2.7.0)
active_model_serializers (0.8.2)
active_model_serializers (0.8.3)
activemodel (>= 3.0)
activemodel (4.1.8)
activesupport (= 4.1.8)
activemodel (4.1.10)
activesupport (= 4.1.10)
builder (~> 3.1)
activerecord (4.1.8)
activemodel (= 4.1.8)
activesupport (= 4.1.8)
activerecord (4.1.10)
activemodel (= 4.1.10)
activesupport (= 4.1.10)
arel (~> 5.0.0)
activesupport (4.1.8)
activesupport (4.1.10)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
addressable (2.3.6)
annotate (2.6.5)
annotate (2.6.6)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
rake (~> 10.4.2, >= 10.4.2)
arel (5.0.1.20140414130214)
barber (0.5.0)
ember-source
execjs
handlebars-source (>= 1.0.0.rc.4)
better_errors (2.1.0)
babel-source (4.6.6)
babel-transpiler (0.6.0)
babel-source (>= 4.0, < 5)
execjs (~> 2.0)
barber (0.9.0)
ember-source (>= 1.0, < 2)
execjs (>= 1.2, < 3)
better_errors (2.1.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
celluloid (0.15.2)
timers (~> 1.1.0)
celluloid (0.16.0)
timers (~> 4.0.0)
certified (1.0.0)
coderay (1.1.0)
connection_pool (2.0.0)
crass (0.2.1)
daemons (1.1.9)
connection_pool (2.1.2)
crass (1.0.1)
daemons (1.2.2)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
discourse-qunit-rails (0.0.8)
railties
docile (1.1.5)
dotenv (0.11.1)
dotenv-deployment (~> 0.0.2)
dotenv-deployment (0.0.2)
dotenv (1.0.2)
email_reply_parser (0.5.8)
ember-data-source (0.14)
ember-source
ember-rails (0.14.1)
ember-data-source (1.0.0.beta.16.1)
ember-source (~> 1.8)
ember-handlebars-template (0.1.5)
barber (>= 0.9.0)
sprockets (>= 2.1, < 3.1)
ember-rails (0.18.2)
active_model_serializers
barber (>= 0.4.1)
ember-data-source
ember-source
execjs (>= 1.2)
handlebars-source
ember-data-source (>= 1.0.0.beta.5)
ember-handlebars-template (>= 0.1.1, < 1.0)
ember-source (>= 1.1.0)
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (1.9.0.beta.4)
handlebars-source (~> 2.0)
ember-source (1.11.3.1)
erubis (2.7.0)
eventmachine (1.0.4)
excon (0.42.1)
execjs (2.2.2)
eventmachine (1.0.7)
excon (0.44.4)
execjs (2.5.2)
exifr (1.1.3)
fabrication (2.9.8)
fakeweb (1.3.0)
faraday (0.9.0)
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
fast_blank (0.0.2)
fast_stack (0.1.0)
@ -97,8 +99,7 @@ GEM
rake
rake-compiler
fast_xs (0.8.0)
fastimage (1.6.3)
addressable (~> 2.3, >= 2.3.5)
fastimage_discourse (1.6.6)
ffi (1.9.6)
fission (0.5.0)
CFPropertyList (~> 2.2)
@ -169,22 +170,23 @@ GEM
fog-xml (0.1.1)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
foreman (0.75.0)
dotenv (~> 0.11.1)
foreman (0.77.0)
dotenv (~> 1.0.2)
thor (~> 0.19.1)
formatador (0.2.5)
fspath (2.0.6)
gctools (0.2.3)
given_core (3.5.4)
sorcerer (>= 0.3.7)
guess_html_encoding (0.0.9)
guess_html_encoding (0.0.11)
handlebars-source (2.0.0)
hashie (3.3.1)
highline (1.6.21)
hashie (3.4.0)
highline (1.7.1)
hike (1.2.3)
hiredis (0.5.2)
hiredis (0.6.0)
hitimes (1.2.2)
htmlentities (4.3.3)
i18n (0.6.11)
i18n (0.7.0)
image_optim (0.9.1)
exifr (~> 1.1.3)
fspath (~> 2.0.5)
@ -198,45 +200,44 @@ GEM
jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.1)
jwt (1.0.0)
kgio (2.9.2)
json (1.8.2)
jwt (1.3.0)
kgio (2.9.3)
librarian (0.1.2)
highline
thor (~> 0.15)
libv8 (3.16.14.7)
listen (0.7.3)
logster (0.1.6)
lru_redux (0.8.1)
logster (0.8.1)
lru_redux (0.8.4)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
memory_profiler (0.0.4)
message_bus (1.0.5)
eventmachine
memory_profiler (0.9.0)
message_bus (1.0.11)
rack (>= 1.1.3)
redis
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.1)
minitest (5.4.2)
mini_portile (0.6.2)
minitest (5.6.1)
mocha (1.1.0)
metaclass (~> 0.0.1)
mock_redis (0.13.2)
mock_redis (0.14.0)
moneta (0.8.0)
msgpack (0.5.10)
multi_json (1.10.1)
msgpack (0.5.11)
multi_json (1.11.0)
multi_xml (0.5.5)
multipart-post (2.0.0)
mustache (0.99.6)
mustache (0.99.8)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (2.9.1)
netrc (0.7.7)
nokogiri (1.6.5)
net-ssh (2.9.2)
netrc (0.10.3)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
nokogumbo (1.1.12)
nokogumbo (1.2.0)
nokogiri
oauth (0.4.7)
oauth2 (1.0.0)
@ -245,7 +246,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
oj (2.11.1)
oj (2.12.0)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
@ -271,7 +272,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
onebox (1.5.10)
onebox (1.5.18)
moneta (~> 0.7)
multi_json (~> 1.7)
mustache (~> 0.99)
@ -279,46 +280,45 @@ GEM
openid-redis-store (0.0.2)
redis
ruby-openid
pg (0.18.0)
pg (0.18.1)
polyglot (0.3.5)
progress (3.0.1)
progress (3.0.2)
pry (0.10.1)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-nav (0.2.4)
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.2)
pry-rails (0.3.3)
pry (>= 0.9.10)
puma (2.9.1)
puma (2.11.1)
rack (>= 1.1, < 2.0)
qunit-rails (0.0.7)
railties
rack (1.5.2)
rack-mini-profiler (0.9.2)
r2 (0.2.5)
rack (1.5.3)
rack-mini-profiler (0.9.3)
rack (>= 1.1.3)
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-protection (1.5.3)
rack
rack-test (0.6.2)
rack-test (0.6.3)
rack (>= 1.0)
rails (4.1.8)
actionmailer (= 4.1.8)
actionpack (= 4.1.8)
actionview (= 4.1.8)
activemodel (= 4.1.8)
activerecord (= 4.1.8)
activesupport (= 4.1.8)
rails (4.1.10)
actionmailer (= 4.1.10)
actionpack (= 4.1.10)
actionview (= 4.1.10)
activemodel (= 4.1.10)
activerecord (= 4.1.10)
activesupport (= 4.1.10)
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.8)
railties (= 4.1.10)
sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
railties (4.1.8)
actionpack (= 4.1.8)
activesupport (= 4.1.8)
railties (4.1.10)
actionpack (= 4.1.10)
activesupport (= 4.1.10)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.13.0)
@ -328,12 +328,12 @@ GEM
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rbtrace (0.4.6)
rbtrace (0.4.7)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
trollop (>= 1.16.2)
redcarpet (3.1.2)
redis (3.2.0)
redcarpet (3.2.2)
redis (3.2.1)
redis-namespace (1.5.1)
redis (~> 3.0, >= 3.0.4)
ref (1.0.5)
@ -342,55 +342,57 @@ GEM
netrc (~> 0.7)
rinku (1.7.3)
rmmseg-cpp (0.2.9)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-collection_matchers (1.0.0)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec (3.2.0)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-core (3.2.3)
rspec-support (~> 3.2.0)
rspec-expectations (3.2.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-given (3.5.4)
given_core (= 3.5.4)
rspec (>= 2.12)
rspec-mocks (2.99.2)
rspec-rails (2.99.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-collection_matchers
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-mocks (3.2.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-rails (3.2.1)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-support (~> 3.2.0)
rspec-support (3.2.2)
rtlit (0.0.5)
ruby-openid (2.5.0)
ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0)
sanitize (3.0.2)
crass (~> 0.2.0)
sanitize (3.1.2)
crass (~> 1.0.1)
nokogiri (>= 1.4.4)
nokogumbo (= 1.1.12)
nokogumbo (= 1.2.0)
sass (3.2.19)
sass-rails (4.0.2)
sass-rails (4.0.5)
railties (>= 4.0.0, < 5.0)
sass (~> 3.2.0)
sprockets (~> 2.8, <= 2.11.0)
sprockets-rails (~> 2.0.0)
seed-fu (2.3.3)
activerecord (>= 3.1, < 4.2)
activesupport (>= 3.1, < 4.2)
seed-fu (2.3.5)
activerecord (>= 3.1, < 4.3)
activesupport (>= 3.1, < 4.3)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0)
shoulda-context (1.2.1)
shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
sidekiq (3.2.5)
celluloid (= 0.15.2)
connection_pool (>= 2.0.0)
sidekiq (3.3.2)
celluloid (>= 0.16.0)
connection_pool (>= 2.1.1)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
@ -420,26 +422,26 @@ GEM
activesupport (>= 3.0)
sprockets (~> 2.8)
stackprof (0.2.7)
stringex (2.5.2)
therubyracer (0.12.1)
therubyracer (0.12.2)
libv8 (~> 3.16.14.0)
ref
thin (1.6.2)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thin (1.6.3)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0)
rack (~> 1.0)
thor (0.19.1)
thread_safe (0.3.4)
thread_safe (0.3.5)
tilt (1.4.1)
timecop (0.7.1)
timers (1.1.0)
timecop (0.7.3)
timers (4.0.1)
hitimes
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
trollop (2.1.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.6.0)
uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
unf (0.1.4)
@ -455,22 +457,24 @@ PLATFORMS
DEPENDENCIES
actionpack-action_caching
active_model_serializers (~> 0.8.0)
active_model_serializers (~> 0.8.3)
annotate
babel-transpiler
barber
better_errors
binding_of_caller
certified
discourse-qunit-rails
email_reply_parser
ember-rails
ember-source (= 1.9.0.beta.4)
ember-source (= 1.11.3.1)
eventmachine
fabrication (= 2.9.8)
fakeweb (~> 1.3.0)
fast_blank
fast_xor
fast_xs
fastimage
fastimage_discourse
flamegraph
fog (= 1.26.0)
foreman
@ -507,10 +511,10 @@ DEPENDENCIES
pry-nav
pry-rails
puma
qunit-rails
r2 (~> 0.2.5)
rack-mini-profiler
rack-protection
rails
rails (~> 4.1.10)
rails-observers
rails_multisite!
rake
@ -522,14 +526,14 @@ DEPENDENCIES
rest-client
rinku
rmmseg-cpp
rspec (= 2.99.0)
rspec (~> 3.2.0)
rspec-given
rspec-rails
rtlit
ruby-readability
sanitize
sass
sass-rails (= 4.0.2)
sass-rails (~> 4.0.5)
seed-fu (~> 2.3.3)
shoulda
sidekiq
@ -538,7 +542,6 @@ DEPENDENCIES
sinatra
spork-rails
stackprof
stringex
therubyracer
thin
timecop

View File

@ -1,552 +0,0 @@
GIT
remote: git://github.com/rails-api/active_model_serializers.git
revision: b6b01d0b7396f3deaa6e661cedf4bc5efe2f4525
branch: 0-8-stable
specs:
active_model_serializers (0.8.2)
activemodel (>= 3.0)
GIT
remote: https://github.com/SamSaffron/rails-observers.git
revision: 7d2222d758603a004f6599f82a7068ffeb2d7ebf
specs:
rails-observers (0.1.2)
activemodel (> 4.0)
GIT
remote: https://github.com/SamSaffron/seed-fu.git
revision: d93df3b6364ea938d87c5629bf950b0d1ffe037e
branch: discourse
specs:
seed-fu (2.3.3)
activerecord (>= 3.1)
activesupport (>= 3.1)
GIT
remote: https://github.com/rails/arel.git
revision: 98fc25991137ee09b6800578117f8c1c322680f2
specs:
arel (6.0.0)
GIT
remote: https://github.com/rails/sass-rails.git
revision: b4b5f32a2928ef203f4b442bc538a572645de8e3
specs:
sass-rails (5.0.0.beta1)
railties (>= 4.0.0, < 5.0)
sass (~> 3.2, >= 3.2.2)
sprockets (~> 2.12)
sprockets-rails (>= 2.0, < 4.0)
PATH
remote: ../rails
specs:
actionmailer (5.0.0.alpha)
actionpack (= 5.0.0.alpha)
actionview (= 5.0.0.alpha)
activejob (= 5.0.0.alpha)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (5.0.0.alpha)
actionview (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
rack (~> 1.6.0.beta2)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
actionview (5.0.0.alpha)
activesupport (= 5.0.0.alpha)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
activejob (5.0.0.alpha)
activesupport (= 5.0.0.alpha)
globalid (>= 0.3.0)
activemodel (5.0.0.alpha)
activesupport (= 5.0.0.alpha)
builder (~> 3.1)
activerecord (5.0.0.alpha)
activemodel (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
arel (~> 6.0)
activesupport (5.0.0.alpha)
i18n (>= 0.7.0.beta1, < 0.8)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
rails (5.0.0.alpha)
actionmailer (= 5.0.0.alpha)
actionpack (= 5.0.0.alpha)
actionview (= 5.0.0.alpha)
activejob (= 5.0.0.alpha)
activemodel (= 5.0.0.alpha)
activerecord (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.0.alpha)
sprockets-rails
railties (5.0.0.alpha)
actionpack (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
PATH
remote: vendor/gems/rails_multisite
specs:
rails_multisite (0.0.1)
GEM
remote: https://rubygems.org/
specs:
actionpack-action_caching (1.1.1)
actionpack (>= 4.0.0, < 5.0)
addressable (2.3.6)
annotate (2.6.5)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
barber (0.4.2)
ember-source
execjs
handlebars-source
better_errors (2.0.0)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
celluloid (0.15.2)
timers (~> 1.1.0)
certified (1.0.0)
coderay (1.1.0)
connection_pool (2.0.0)
crass (0.2.1)
daemons (1.1.9)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
docile (1.1.5)
dotenv (0.11.1)
dotenv-deployment (~> 0.0.2)
dotenv-deployment (0.0.2)
email_reply_parser-discourse (0.6)
ember-data-source (0.14)
ember-source
ember-rails (0.14.1)
active_model_serializers
barber (>= 0.4.1)
ember-data-source
ember-source
execjs (>= 1.2)
handlebars-source
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (1.6.0.beta.2)
handlebars-source (~> 1.0)
erubis (2.7.0)
eventmachine (1.0.3)
excon (0.39.6)
execjs (2.2.1)
exifr (1.1.3)
fabrication (2.9.8)
fakeweb (1.3.0)
faraday (0.9.0)
multipart-post (>= 1.2, < 3)
fast_blank (0.0.2)
fast_stack (0.1.0)
rake
rake-compiler
fast_xor (1.1.3)
rake
rake-compiler
fast_xs (0.8.0)
fastimage (1.6.3)
addressable (~> 2.3, >= 2.3.5)
ffi (1.9.5)
flamegraph (0.0.9)
fast_stack
fog (1.22.1)
fog-brightbox
fog-core (~> 1.22)
fog-json
ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
fog-brightbox (0.5.1)
fog-core (~> 1.22)
fog-json
inflecto
fog-core (1.24.0)
builder
excon (~> 0.38)
formatador (~> 0.2)
mime-types
net-scp (~> 1.1)
net-ssh (>= 2.1.3)
fog-json (1.0.0)
multi_json (~> 1.0)
foreman (0.75.0)
dotenv (~> 0.11.1)
thor (~> 0.19.1)
formatador (0.2.5)
fspath (2.0.6)
gctools (0.2.3)
given_core (3.5.4)
sorcerer (>= 0.3.7)
globalid (0.3.0)
activesupport (>= 4.1.0)
guess_html_encoding (0.0.9)
handlebars-source (1.3.0)
hashie (3.3.1)
highline (1.6.21)
hike (1.2.3)
hiredis (0.5.2)
htmlentities (4.3.2)
i18n (0.7.0.beta1)
image_optim (0.9.1)
exifr (~> 1.1.3)
fspath (~> 2.0.5)
image_size (~> 1.1.2)
in_threads (~> 1.2.0)
progress (~> 3.0.0)
image_size (1.1.5)
in_threads (1.2.2)
inflecto (0.0.2)
ipaddress (0.8.0)
jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.1)
jwt (1.0.0)
kgio (2.9.2)
librarian (0.1.2)
highline
thor (~> 0.15)
libv8 (3.16.14.7)
listen (0.7.3)
logster (0.1.6)
loofah (2.0.1)
nokogiri (>= 1.5.9)
lru_redux (0.8.1)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
memory_profiler (0.0.4)
message_bus (1.0.5)
eventmachine
rack (>= 1.1.3)
redis
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.1)
minitest (5.4.3)
mocha (1.1.0)
metaclass (~> 0.0.1)
mock_redis (0.13.2)
moneta (0.8.0)
msgpack (0.5.8)
multi_json (1.10.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
mustache (0.99.6)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (2.9.1)
netrc (0.7.7)
nokogiri (1.6.4.1)
mini_portile (~> 0.6.0)
nokogumbo (1.1.12)
nokogiri
oauth (0.4.7)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
oj (2.10.2)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
omniauth-facebook (2.0.0)
omniauth-oauth2 (~> 1.2)
omniauth-github-discourse (1.1.2)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-google-oauth2 (0.2.5)
omniauth (> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-oauth (1.0.1)
oauth
omniauth (~> 1.0)
omniauth-oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
multi_json (~> 1.3)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-openid (1.0.1)
omniauth (~> 1.0)
rack-openid (~> 1.3.1)
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
onebox (1.5.3)
moneta (~> 0.7)
multi_json (~> 1.7)
mustache (~> 0.99)
nokogiri (~> 1.6.1)
openid-redis-store (0.0.2)
redis
ruby-openid
pg (0.18.0.pre20141117110243)
polyglot (0.3.5)
progress (3.0.1)
pry (0.10.1)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-nav (0.2.4)
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.2)
pry (>= 0.9.10)
puma (2.9.1)
rack (>= 1.1, < 2.0)
qunit-rails (0.0.7)
railties
rack (1.6.0.beta2)
rack-mini-profiler (0.9.2)
rack (>= 1.1.3)
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-protection (1.5.3)
rack
rack-test (0.6.2)
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.5)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.1)
loofah (~> 2.0)
raindrops (0.13.0)
rake (10.4.0)
rake-compiler (0.9.3)
rake
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rbtrace (0.4.5)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
trollop (>= 1.16.2)
redcarpet (3.1.2)
redis (3.1.0)
redis-namespace (1.5.1)
redis (~> 3.0, >= 3.0.4)
ref (1.0.5)
rest-client (1.7.2)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rinku (1.7.3)
rmmseg-cpp (0.2.9)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-collection_matchers (1.0.0)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-given (3.5.4)
given_core (= 3.5.4)
rspec (>= 2.12)
rspec-mocks (2.99.2)
rspec-rails (2.99.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-collection_matchers
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rtlit (0.0.5)
ruby-openid (2.5.0)
ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0)
sanitize (3.0.2)
crass (~> 0.2.0)
nokogiri (>= 1.4.4)
nokogumbo (= 1.1.12)
sass (3.2.19)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0)
shoulda-context (1.2.1)
shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
sidekiq (3.2.5)
celluloid (= 0.15.2)
connection_pool (>= 2.0.0)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
simple-rss (1.3.1)
simplecov (0.9.1)
docile (~> 1.1.0)
multi_json (~> 1.0)
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
sinatra (1.4.5)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
slop (3.6.0)
sorcerer (1.0.2)
spork (1.0.0rc4)
spork-rails (4.0.0)
rails (>= 3.0.0, < 5)
spork (>= 1.0rc0)
sprockets (2.12.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (3.0.0.beta1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (~> 2.8)
stackprof (0.2.7)
stringex (2.5.2)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
thin (1.6.2)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thor (0.19.1)
thread_safe (0.3.4)
tilt (1.4.1)
timecop (0.7.1)
timers (1.1.0)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
trollop (2.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.5.3)
execjs (>= 0.3.0)
json (>= 1.8.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.6)
unicorn (4.8.3)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
PLATFORMS
ruby
DEPENDENCIES
actionpack-action_caching
active_model_serializers!
annotate
arel!
barber
better_errors
binding_of_caller
certified
email_reply_parser-discourse
ember-rails
ember-source (= 1.6.0.beta.2)
eventmachine
fabrication (= 2.9.8)
fakeweb (~> 1.3.0)
fast_blank
fast_xor
fast_xs
fastimage
flamegraph
fog (= 1.22.1)
foreman
gctools
handlebars-source (= 1.3.0)
highline
hiredis
htmlentities
image_optim (= 0.9.1)
librarian (>= 0.0.25)
listen (= 0.7.3)
logster
lru_redux
mail (~> 2.5.4)
memory_profiler
message_bus
minitest
mocha
mock_redis
multi_json
mustache
nokogiri
oj
omniauth
omniauth-facebook
omniauth-github-discourse
omniauth-google-oauth2
omniauth-oauth2
omniauth-openid
omniauth-twitter
onebox
openid-redis-store
pg (= 0.18.0.pre20141117110243)
pry-nav
pry-rails
puma
qunit-rails
rack-mini-profiler
rack-protection
rails!
rails-observers!
rails_multisite!
rake
rb-fsevent
rb-inotify (~> 0.9)
rbtrace
redcarpet
redis
rest-client
rinku
rmmseg-cpp
rspec (= 2.99.0)
rspec-given
rspec-rails
rtlit
ruby-readability
sanitize
sass
sass-rails!
seed-fu!
shoulda
sidekiq
simple-rss
simplecov
sinatra
spork-rails
stackprof
stringex
therubyracer
thin
timecop
uglifier
unf
unicorn

View File

@ -10,13 +10,14 @@ To learn more about the philosophy and goals of the project, [visit **discourse.
## Screenshots
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/boing-boing-latest-small2.png)](http://bbs.boingboing.net)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/how-to-geek-profile-small2.png)](http://discuss.howtogeek.com)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/new-relic-categories-small2.png)](http://discuss.newrelic.com)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/turtle-rock-topic-small2.jpg)](https://talk.turtlerockstudios.com/)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/nexus-7-mobile-discourse-small3.png)](http://discuss.atom.io)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/iphone-5s-mobile-discourse-small4.png)](http://discourse.soylent.me)
[![](http://www.discourse.org/images/readme/boingboing-1-3-beta.png)](http://bbs.boingboing.net)
[![](http://www.discourse.org/images/readme/howtogeek-1-3-beta.png)](http://discuss.howtogeek.com)
[![](http://www.discourse.org/images/readme/newrelic-1-3-beta.png)](http://discuss.newrelic.com)
[![](http://www.discourse.org/images/readme/turtlerock-1-3-beta.png)](https://talk.turtlerockstudios.com/)
[![](http://www.discourse.org/images/readme/android-tablet-discourse-1-3-beta.png?v=3)](http://discuss.atom.io)
[![](http://www.discourse.org/images/readme/iphone-6-discourse-1-3-beta.png?v=3)](http://discourse.soylent.me)
Browse [lots more notable Discourse instances](http://www.discourse.org/faq/customers/).
## Development
@ -82,7 +83,7 @@ The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/A
## Copyright / License
Copyright 2014 Civilized Discourse Construction Kit, Inc.
Copyright 2014 - 2015 Civilized Discourse Construction Kit, Inc.
Licensed under the GNU General Public License Version 2.0 (or later);
you may not use this work except in compliance with the License.

0
Rakefile Normal file → Executable file
View File

21
Vagrantfile vendored
View File

@ -3,8 +3,8 @@
# See https://github.com/discourse/discourse/blob/master/docs/VAGRANT.md
#
Vagrant.configure("2") do |config|
config.vm.box= "discourse/discourse-0.9.9.15.box"
config.vm.box_url = "https://vagrantcloud.com/discourse/discourse-0.9.9.15.box"
config.vm.box = 'discourse/discourse-1.3.0'
config.vm.box_url = "http://discourse-vms.s3.amazonaws.com/discourse-1.3.0.box"
# Make this VM reachable on the host network as well, so that other
# VM's running other browsers can access our dev server.
@ -45,21 +45,4 @@ Vagrant.configure("2") do |config|
nfs_setting = RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /linux/
config.vm.synced_folder ".", "/vagrant", id: "vagrant-root", :nfs => nfs_setting
config.vm.provision :shell, :inline => "apt-get -qq update && apt-get -qq -y install ruby1.9.3 build-essential && gem install chef --no-rdoc --no-ri --conservative"
chef_cookbooks_path = ["chef/cookbooks"]
# This run uses the updated chef-solo and does normal configuration
config.vm.provision :chef_solo do |chef|
chef.binary_env = "GEM_HOME=/opt/chef/embedded/lib/ruby/gems/1.9.1/ GEM_PATH= "
chef.binary_path = "/opt/chef/bin/"
chef.cookbooks_path = chef_cookbooks_path
chef.add_recipe "recipe[apt]"
chef.add_recipe "recipe[build-essential]"
chef.add_recipe "recipe[vim]"
chef.add_recipe "recipe[java]"
chef.add_recipe "recipe[imagemagick]"
chef.add_recipe "discourse"
end
end

Binary file not shown.

BIN
app/assets/fonts/fontawesome-webfont.eot Executable file → Normal file

Binary file not shown.

989
app/assets/fonts/fontawesome-webfont.svg Executable file → Normal file

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 306 KiB

BIN
app/assets/fonts/fontawesome-webfont.ttf Executable file → Normal file

Binary file not shown.

BIN
app/assets/fonts/fontawesome-webfont.woff Executable file → Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,53 @@
/* global ace:true */
import loadScript from 'discourse/lib/load-script';
export default Ember.Component.extend({
mode: 'css',
classNames: ['ace-wrapper'],
_editor: null,
_skipContentChangeEvent: null,
contentChanged: function() {
if (this._editor && !this._skipContentChangeEvent) {
this._editor.getSession().setValue(this.get('content'));
}
}.observes('content'),
render(buffer) {
buffer.push("<div class='ace'>");
if (this.get('content')) {
buffer.push(Handlebars.Utils.escapeExpression(this.get('content')));
}
buffer.push("</div>");
},
_destroyEditor: function() {
if (this._editor) {
this._editor.destroy();
this._editor = null;
}
}.on('willDestroyElement'),
_initEditor: function() {
const self = this;
loadScript("/javascripts/ace/ace.js", { scriptTag: true }).then(function() {
ace.require(['ace/ace'], function(loadedAce) {
const editor = loadedAce.edit(self.$('.ace')[0]);
editor.setTheme("ace/theme/chrome");
editor.setShowPrintMargin(false);
editor.getSession().setMode("ace/mode/" + self.get('mode'));
editor.on('change', function() {
self._skipContentChangeEvent = true;
self.set('content', editor.getSession().getValue());
self._skipContentChangeEvent = false;
});
self.$().data('editor', editor);
self._editor = editor;
});
});
}.on('didInsertElement')
});

View File

@ -1,31 +1,36 @@
export default Ember.Component.extend({
tagName: 'div',
didInsertElement: function(){
_init: function(){
this.$("input").select2({
multiple: true,
width: '100%',
query: function(opts) {
opts.callback({
results: this.get("available").map(this._format)
});
opts.callback({ results: this.get("available").map(this._format) });
}.bind(this)
}).on("change", function(evt) {
if (evt.added){
this.triggerAction({action: "groupAdded",
actionContext: this.get("available"
).findBy("id", evt.added.id)});
this.triggerAction({
action: "groupAdded",
actionContext: this.get("available").findBy("id", evt.added.id)
});
} else if (evt.removed) {
this.triggerAction({action:"groupRemoved",
actionContext: this.get("selected"
).findBy("id", evt.removed.id)});
this.triggerAction({
action:"groupRemoved",
actionContext: evt.removed.id
});
}
}.bind(this));
this._refreshOnReset();
},
_format: function(item){
return {"text": item.name, "id": item.id, "locked": item.automatic};
this._refreshOnReset();
}.on("didInsertElement"),
_format(item) {
return {
"text": item.name,
"id": item.id,
"locked": item.automatic
};
},
_refreshOnReset: function() {

View File

@ -0,0 +1,18 @@
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['active'],
router: function() {
return this.container.lookup('router:main');
}.property(),
active: function() {
const route = this.get('route');
if (!route) { return; }
const routeParam = this.get('routeParam'),
router = this.get('router');
return routeParam ? router.isActive(route, routeParam) : router.isActive(route);
}.property('router.url', 'route')
});

View File

@ -0,0 +1,3 @@
export default Ember.Component.extend({
tagName: 'tr'
});

View File

@ -0,0 +1,3 @@
export default Ember.Component.extend({
tagName: 'tr'
});

View File

@ -0,0 +1,3 @@
export default Ember.Component.extend({
tagName: 'tr'
});

View File

@ -21,7 +21,8 @@ export default Ember.Component.extend({
tokenSeparators: ["|"],
tags : this.get("choices") || [],
width: 'off',
dropdownCss: this.get("choices") ? {} : {display: 'none'}
dropdownCss: this.get("choices") ? {} : {display: 'none'},
selectOnBlur: this.get("choices") ? false : true
};
var settingName = this.get('settingName');

View File

@ -14,6 +14,7 @@ Discourse.ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuff
tagName: "button",
classNames: ["btn", "ru"],
classNameBindings: ["isUploading"],
attributeBindings: ["translatedTitle:title"],
resumable: null,
@ -22,6 +23,11 @@ Discourse.ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuff
rerenderTriggers: ['isUploading', 'progress'],
translatedTitle: function() {
const title = this.get('title');
return title ? I18n.t(title) : this.get('text');
}.property('title', 'text'),
text: function() {
if (this.get("isUploading")) {
return this.get("progress") + " %";

View File

@ -0,0 +1,91 @@
import BufferedContent from 'discourse/mixins/buffered-content';
import SiteSetting from 'admin/models/site-setting';
const CustomTypes = ['bool', 'enum', 'list', 'url_list'];
export default Ember.Component.extend(BufferedContent, Discourse.ScrollTop, {
classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'],
content: Ember.computed.alias('setting'),
dirty: Discourse.computed.propertyNotEqual('buffered.value', 'setting.value'),
validationMessage: null,
preview: function() {
const preview = this.get('setting.preview');
if (preview) {
return new Handlebars.SafeString("<div class='preview'>" +
preview.replace("{{value}}", this.get('buffered.value')) +
"</div>");
}
}.property('buffered.value'),
typeClass: function() {
return this.get('partialType').replace("_", "-");
}.property('partialType'),
enabled: function(key, value) {
if (arguments.length > 1) {
this.set('buffered.value', value ? 'true' : 'false');
}
const bufferedValue = this.get('buffered.value');
if (Ember.isEmpty(bufferedValue)) { return false; }
return bufferedValue === 'true';
}.property('buffered.value'),
settingName: function() {
return this.get('setting.setting').replace(/\_/g, ' ');
}.property('setting.setting'),
partialType: function() {
let type = this.get('setting.type');
return (CustomTypes.indexOf(type) !== -1) ? type : 'string';
}.property('setting.type'),
partialName: function() {
return 'admin/templates/site-settings/' + this.get('partialType');
}.property('partialType'),
_watchEnterKey: function() {
const self = this;
this.$().on("keydown.site-setting-enter", ".input-setting-string", function (e) {
if (e.keyCode === 13) { // enter key
self._save();
}
});
}.on('didInsertElement'),
_removeBindings: function() {
this.$().off("keydown.site-setting-enter");
}.on("willDestroyElement"),
_save() {
const setting = this.get('buffered');
const self = this;
SiteSetting.update(setting.get('setting'), setting.get('value')).then(function() {
self.set('validationMessage', null);
self.commitBuffer();
}).catch(function(e) {
if (e.responseJSON && e.responseJSON.errors) {
self.set('validationMessage', e.responseJSON.errors[0]);
} else {
self.set('validationMessage', I18n.t('generic_error'));
}
});
},
actions: {
save() {
this._save();
},
resetDefault() {
this.set('buffered.value', this.get('setting.default'));
this._save();
},
cancel() {
this.rollbackBuffer();
}
}
});

View File

@ -0,0 +1,32 @@
export default Ember.Component.extend({
_setupUrls: function() {
const value = this.get('value');
this.set('urls', (value && value.length) ? value.split("\n") : []);
}.on('init').observes('value'),
_urlsChanged: function() {
this.set('value', this.get('urls').join("\n"));
}.observes('urls.@each'),
urlInvalid: Ember.computed.empty('newUrl'),
keyDown(e) {
if (e.keyCode === 13) {
this.send('addUrl');
}
},
actions: {
addUrl() {
if (this.get('urlInvalid')) { return; }
this.get('urls').addObject(this.get('newUrl'));
this.set('newUrl', '');
},
removeUrl(url) {
const urls = this.get('urls');
urls.removeObject(url);
}
}
});

View File

@ -1,49 +1,24 @@
export default Ember.ArrayController.extend({
needs: ["adminBackups"],
status: Em.computed.alias("controllers.adminBackups"),
uploadText: function() { return I18n.t("admin.backups.upload.text"); }.property(),
readOnlyModeDisabled: Em.computed.alias("status.isOperationRunning"),
isOperationRunning: Em.computed.alias("status.isOperationRunning"),
restoreDisabled: Em.computed.alias("status.restoreDisabled"),
uploadLabel: function() { return I18n.t("admin.backups.upload.label"); }.property(),
restoreTitle: function() {
if (!this.get('status.allowRestore')) {
return I18n.t("admin.backups.operations.restore.is_disabled");
return "admin.backups.operations.restore.is_disabled";
} else if (this.get("status.isOperationRunning")) {
return I18n.t("admin.backups.operation_already_running");
return "admin.backups.operations.is_running";
} else {
return I18n.t("admin.backups.operations.restore.title");
return "admin.backups.operations.restore.title";
}
}.property("status.isOperationRunning"),
destroyDisabled: Em.computed.alias("status.isOperationRunning"),
destroyTitle: function() {
if (this.get("status.isOperationRunning")) {
return I18n.t("admin.backups.operation_already_running");
} else {
return I18n.t("admin.backups.operations.destroy.title");
}
}.property("status.isOperationRunning"),
readOnlyModeTitle: function() { return this._readOnlyModeI18n("title"); }.property("site.isReadOnly"),
readOnlyModeText: function() { return this._readOnlyModeI18n("text"); }.property("site.isReadOnly"),
_readOnlyModeI18n: function(value) {
var action = this.site.get("isReadOnly") ? "disable" : "enable";
return I18n.t("admin.backups.read_only." + action + "." + value);
},
}.property("status.{allowRestore,isOperationRunning}"),
actions: {
/**
Toggle read-only mode
@method toggleReadOnlyMode
**/
toggleReadOnlyMode: function() {
toggleReadOnlyMode() {
var self = this;
if (!this.site.get("isReadOnly")) {
bootbox.confirm(
@ -64,7 +39,7 @@ export default Ember.ArrayController.extend({
},
_toggleReadOnlyMode: function(enable) {
_toggleReadOnlyMode(enable) {
var site = this.site;
Discourse.ajax("/admin/backups/readonly", {
type: "PUT",

View File

@ -53,6 +53,9 @@ export default Ember.Controller.extend({
actions: {
refreshProblems: function() {
this.loadProblems();
},
showTrafficReport: function() {
this.set("showTrafficReport", true);
}
}

View File

@ -5,7 +5,7 @@ export default Ember.ArrayController.extend({
adminActiveFlagsView: Em.computed.equal("query", "active"),
actions: {
disagreeFlags: function (flaggedPost) {
disagreeFlags(flaggedPost) {
var self = this;
flaggedPost.disagreeFlags().then(function () {
self.removeObject(flaggedPost);
@ -14,7 +14,7 @@ export default Ember.ArrayController.extend({
});
},
deferFlags: function (flaggedPost) {
deferFlags(flaggedPost) {
var self = this;
flaggedPost.deferFlags().then(function () {
self.removeObject(flaggedPost);
@ -23,12 +23,12 @@ export default Ember.ArrayController.extend({
});
},
doneTopicFlags: function(item) {
doneTopicFlags(item) {
this.send("disagreeFlags", item);
},
},
loadMore: function(){
loadMore(){
var flags = this.get("model");
return Discourse.FlaggedPost.findAll(this.get("query"),flags.length+1).then(function(data){
if(data.length===0){

View File

@ -3,12 +3,12 @@ export default Em.ObjectController.extend({
disableSave: false,
currentPage: function() {
if (this.get("user_count") == 0) { return 0; }
if (this.get("user_count") === 0) { return 0; }
return Math.floor(this.get("offset") / this.get("limit")) + 1;
}.property("limit", "offset", "user_count"),
totalPages: function() {
if (this.get("user_count") == 0) { return 0; }
if (this.get("user_count") === 0) { return 0; }
return Math.floor(this.get("user_count") / this.get("limit")) + 1;
}.property("limit", "user_count"),
@ -25,10 +25,10 @@ export default Em.ObjectController.extend({
}.property(),
actions: {
next: function() {
next() {
if (this.get("showingLast")) { return; }
var group = this.get("model"),
const group = this.get("model"),
offset = Math.min(group.get("offset") + group.get("limit"), group.get("user_count"));
group.set("offset", offset);
@ -36,10 +36,10 @@ export default Em.ObjectController.extend({
return group.findMembers();
},
previous: function() {
previous() {
if (this.get("showingFirst")) { return; }
var group = this.get("model"),
const group = this.get("model"),
offset = Math.max(group.get("offset") - group.get("limit"), 0);
group.set("offset", offset);
@ -47,8 +47,8 @@ export default Em.ObjectController.extend({
return group.findMembers();
},
removeMember: function(member) {
var self = this,
removeMember(member) {
const self = this,
message = I18n.t("admin.groups.delete_member_confirm", { username: member.get("username"), group: this.get("name") });
return bootbox.confirm(message, I18n.t("no_value"), I18n.t("yes_value"), function(confirm) {
if (confirm) {
@ -57,57 +57,49 @@ export default Em.ObjectController.extend({
});
},
addMembers: function() {
addMembers() {
if (Em.isEmpty(this.get("usernames"))) { return; }
this.get("model").addMembers(this.get("usernames"));
// clear the user selector
this.set("usernames", null);
},
save: function() {
var self = this,
group = this.get('model'),
save() {
const group = this.get('model'),
groupsController = this.get("controllers.adminGroupsType");
this.set('disableSave', true);
var promise;
if (group.get("id")) {
promise = group.save();
} else {
promise = group.create().then(function() {
groupsController.addObject(group);
});
}
promise.then(function() {
self.transitionToRoute("adminGroup", group);
}, function(e) {
var message = $.parseJSON(e.responseText).errors;
bootbox.alert(message);
}).finally(function() {
self.set('disableSave', false);
});
let promise = group.get("id") ? group.save() : group.create().then(() => groupsController.addObject(group));
promise.then(() => this.transitionToRoute("adminGroup", group))
.catch(e => bootbox.alert($.parseJSON(e.responseText).errors))
.finally(() => this.set('disableSave', false));
},
destroy: function() {
var group = this.get('model'),
destroy() {
const group = this.get('model'),
groupsController = this.get('controllers.adminGroupsType'),
self = this;
this.set('disableSave', true);
bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(confirmed) {
bootbox.confirm(
I18n.t("admin.groups.delete_confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
if (confirmed) {
group.destroy().then(function() {
group.destroy().then(() => {
groupsController.get('model').removeObject(group);
self.transitionToRoute('adminGroups.index');
}, function() {
bootbox.alert(I18n.t("admin.groups.delete_failed"));
}).finally(function() {
}).catch(() => bootbox.alert(I18n.t("admin.groups.delete_failed")))
.finally(() => self.set('disableSave', false));
} else {
self.set('disableSave', false);
});
}
});
}
);
}
}
});

View File

@ -1,6 +1,9 @@
export default Ember.ArrayController.extend({
sortProperties: ['name'],
refreshingAutoGroups: false,
isAuto: function(){
return this.get('type') === 'automatic';
}.property('type'),
actions: {
refreshAutoGroups: function(){

View File

@ -3,8 +3,8 @@ export default Ember.ObjectController.extend({
savedIpAddress: null,
isRange: function() {
return this.get("ip_address").indexOf("/") > 0;
}.property("ip_address"),
return this.get("model.ip_address").indexOf("/") > 0;
}.property("model.ip_address"),
actions: {
allow: function(record) {
@ -19,14 +19,14 @@ export default Ember.ObjectController.extend({
edit: function() {
if (!this.get('editing')) {
this.savedIpAddress = this.get('ip_address');
this.savedIpAddress = this.get('model.ip_address');
}
this.set('editing', true);
},
cancel: function() {
if (this.get('savedIpAddress') && this.get('editing')) {
this.set('ip_address', this.get('savedIpAddress'));
this.set('model.ip_address', this.get('savedIpAddress'));
}
this.set('editing', false);
},

View File

@ -1,22 +1,22 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
export default Ember.ArrayController.extend({
loading: false,
actions: {
clearBlock: function(row){
clearBlock(row){
row.clearBlock().then(function(){
// feeling lazy
window.location.reload();
});
},
exportScreenedEmailList: function(subject) {
exportScreenedEmailList() {
Discourse.ExportCsv.exportScreenedEmailList().then(outputExportResult);
}
},
show: function() {
show() {
var self = this;
self.set('loading', true);
Discourse.ScreenedEmail.findAll().then(function(result) {

View File

@ -1,28 +1,29 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
export default Ember.ArrayController.extend({
loading: false,
itemController: 'admin-log-screened-ip-address',
filter: null,
show: function() {
show: Discourse.debounce(function() {
var self = this;
self.set('loading', true);
Discourse.ScreenedIpAddress.findAll().then(function(result) {
Discourse.ScreenedIpAddress.findAll(this.get("filter")).then(function(result) {
self.set('model', result);
self.set('loading', false);
});
},
}, 250).observes("filter"),
actions: {
recordAdded: function(arg) {
recordAdded(arg) {
this.get("model").unshiftObject(arg);
},
rollUp: function() {
var self = this;
rollUp() {
const self = this;
return bootbox.confirm(I18n.t("admin.logs.screened_ips.roll_up_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function (confirmed) {
if (confirmed) {
self.set("loading", true)
self.set("loading", true);
return Discourse.ScreenedIpAddress.rollUp().then(function(results) {
if (results && results.subnets) {
if (results.subnets.length > 0) {
@ -38,7 +39,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
});
},
exportScreenedIpList: function(subject) {
exportScreenedIpList() {
Discourse.ExportCsv.exportScreenedIpList().then(outputExportResult);
}
}

View File

@ -1,10 +1,10 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
export default Ember.ArrayController.extend({
loading: false,
show: function() {
var self = this;
show() {
const self = this;
self.set('loading', true);
Discourse.ScreenedUrl.findAll().then(function(result) {
self.set('model', result);
@ -13,7 +13,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
},
actions: {
exportScreenedUrlList: function(subject) {
exportScreenedUrlList() {
Discourse.ExportCsv.exportScreenedUrlList().then(outputExportResult);
}
}

View File

@ -1,70 +1,97 @@
/**
This controller supports the interface for listing staff action logs in the admin section.
@class AdminLogsStaffActionLogsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
export default Ember.ArrayController.extend({
loading: false,
filters: {},
filters: null,
show: function() {
var self = this;
this.set('loading', true);
Discourse.URL.set('queryParams', this.get('filters')); // TODO: doesn't work
Discourse.StaffActionLog.findAll(this.get('filters')).then(function(result) {
self.set('model', result);
self.set('loading', false);
});
}.observes('filters.action_name', 'filters.acting_user', 'filters.target_user', 'filters.subject'),
filtersExists: function() {
return (_.size(this.get('filters')) > 0);
}.property('filters.action_name', 'filters.acting_user', 'filters.target_user', 'filters.subject'),
filtersExists: Ember.computed.gt('filterCount', 0),
actionFilter: function() {
if (this.get('filters.action_name')) {
return I18n.t("admin.logs.staff_actions.actions." + this.get('filters.action_name'));
var name = this.get('filters.action_name');
if (name) {
return I18n.t("admin.logs.staff_actions.actions." + name);
} else {
return null;
}
}.property('filters.action_name'),
showInstructions: function() {
return this.get('model.length') > 0;
}.property('loading', 'model.length'),
showInstructions: Ember.computed.gt('model.length', 0),
refresh: function() {
var self = this;
this.set('loading', true);
var filters = this.get('filters'),
params = {},
count = 0;
// Don't send null values
Object.keys(filters).forEach(function(k) {
var val = filters.get(k);
if (val) {
params[k] = val;
count += 1;
}
});
this.set('filterCount', count);
Discourse.StaffActionLog.findAll(params).then(function(result) {
self.set('model', result);
}).finally(function() {
self.set('loading', false);
});
},
resetFilters: function() {
this.set('filters', Ember.Object.create());
this.refresh();
}.on('init'),
_changeFilters: function(props) {
this.get('filters').setProperties(props);
this.refresh();
},
actions: {
clearFilter: function(key) {
delete this.get('filters')[key];
this.notifyPropertyChange('filters');
var changed = {};
// Special case, clear all action related stuff
if (key === 'actionFilter') {
changed.action_name = null;
changed.action_id = null;
changed.custom_type = null;
} else {
changed[key] = null;
}
this._changeFilters(changed);
},
clearAllFilters: function() {
this.set('filters', {});
this.resetFilters();
},
filterByAction: function(action) {
this.set('filters.action_name', action);
filterByAction: function(logItem) {
this._changeFilters({
action_name: logItem.get('action_name'),
action_id: logItem.get('action'),
custom_type: logItem.get('custom_type')
});
},
filterByStaffUser: function(acting_user) {
this.set('filters.acting_user', acting_user.username);
this._changeFilters({ acting_user: acting_user.username });
},
filterByTargetUser: function(target_user) {
this.set('filters.target_user', target_user.username);
this._changeFilters({ target_user: target_user.username });
},
filterBySubject: function(subject) {
this.set('filters.subject', subject);
this._changeFilters({ subject: subject });
},
exportStaffActionLogs: function(subject) {
exportStaffActionLogs: function() {
Discourse.ExportCsv.exportStaffActionLogs().then(outputExportResult);
}
}

View File

@ -0,0 +1,6 @@
export default Ember.ArrayController.extend({
adminRoutes: function() {
return this.get('model').map(p => p.admin_route).compact();
}.property()
});

View File

@ -5,46 +5,12 @@ export default Ember.ObjectController.extend({
filteredContent: function() {
if (!this.get('categoryNameKey')) { return []; }
var category = this.get('controllers.adminSiteSettings.content').findProperty('nameKey', this.get('categoryNameKey'));
const category = this.get('controllers.adminSiteSettings.content').findProperty('nameKey', this.get('categoryNameKey'));
if (category) {
return category.siteSettings;
} else {
return [];
}
}.property('controllers.adminSiteSettings.content', 'categoryNameKey'),
actions: {
/**
Reset a setting to its default value
@method resetDefault
@param {Discourse.SiteSetting} setting The setting we want to revert
**/
resetDefault: function(setting) {
setting.set('value', setting.get('default'));
setting.save();
},
/**
Save changes to a site setting
@method save
@param {Discourse.SiteSetting} setting The setting we've changed
**/
save: function(setting) {
setting.save();
},
/**
Cancel changes to a site setting
@method cancel
@param {Discourse.SiteSetting} setting The setting we've changed but want to revert
**/
cancel: function(setting) {
setting.resetValue();
}
}
}.property('controllers.adminSiteSettings.content', 'categoryNameKey')
});

View File

@ -1,19 +1,16 @@
export default Ember.ArrayController.extend(Discourse.Presence, {
import Presence from 'discourse/mixins/presence';
export default Ember.ArrayController.extend(Presence, {
filter: null,
onlyOverridden: false,
filtered: Ember.computed.notEmpty('filter'),
/**
The list of settings based on the current filters
@property filterContent
**/
filterContent: Discourse.debounce(function() {
// If we have no content, don't bother filtering anything
if (!this.present('allSiteSettings')) return;
var filter;
let filter;
if (this.get('filter')) {
filter = this.get('filter').toLowerCase();
}
@ -24,12 +21,11 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
return;
}
var self = this,
matches,
matchesGroupedByCategory = Em.A([{nameKey: 'all_results', name: I18n.t('admin.site_settings.categories.all_results'), siteSettings: []}]);
const self = this,
matchesGroupedByCategory = [{nameKey: 'all_results', name: I18n.t('admin.site_settings.categories.all_results'), siteSettings: []}];
_.each(this.get('allSiteSettings'), function(settingsCategory) {
matches = settingsCategory.siteSettings.filter(function(item) {
this.get('allSiteSettings').forEach(function(settingsCategory) {
const matches = settingsCategory.siteSettings.filter(function(item) {
if (self.get('onlyOverridden') && !item.get('overridden')) return false;
if (filter) {
if (item.get('setting').toLowerCase().indexOf(filter) > -1) return true;
@ -51,7 +47,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
}, 250).observes('filter', 'onlyOverridden'),
actions: {
clearFilter: function() {
clearFilter() {
this.setProperties({
filter: '',
onlyOverridden: false

View File

@ -13,6 +13,42 @@ export default Ember.ArrayController.extend({
sortProperties: ['granted_at'],
sortAscending: false,
groupedBadges: function(){
const badges = this.get('model');
var grouped = _.groupBy(badges, badge => badge.badge_id);
var expanded = [];
const expandedBadges = badges.get('expandedBadges');
_(grouped).each(function(badges){
var lastGranted = badges[0].granted_at;
_.each(badges, function(badge) {
lastGranted = lastGranted < badge.granted_at ? badge.granted_at : lastGranted;
});
if(badges.length===1 || _.include(expandedBadges, badges[0].badge.id)){
_.each(badges, badge => expanded.push(badge));
return;
}
var result = {
badge: badges[0].badge,
granted_at: lastGranted,
badges: badges,
count: badges.length,
grouped: true
};
expanded.push(result);
});
return _(expanded).sortBy(group => group.granted_at).reverse().value();
}.property('model', 'model.@each', 'model.expandedBadges.@each'),
/**
Array of badges that have not been granted to this user.
@ -32,7 +68,7 @@ export default Ember.ArrayController.extend({
}
});
return badges;
return _.sortBy(badges, "name");
}.property('badges.@each', 'model.@each'),
/**
@ -45,6 +81,12 @@ export default Ember.ArrayController.extend({
actions: {
expandGroup: function(userBadge){
const model = this.get('model');
model.set('expandedBadges', model.get('expandedBadges') || []);
model.get('expandedBadges').pushObject(userBadge.badge.id);
},
/**
Grant the selected badge to the user.
@ -53,7 +95,8 @@ export default Ember.ArrayController.extend({
**/
grantBadge: function(badgeId) {
var self = this;
Discourse.UserBadge.grant(badgeId, this.get('user.username')).then(function(userBadge) {
Discourse.UserBadge.grant(badgeId, this.get('user.username'), this.get('badgeReason')).then(function(userBadge) {
self.set('badgeReason', '');
self.pushObject(userBadge);
Ember.run.next(function() {
// Update the selected badge ID after the combobox has re-rendered.

View File

@ -11,17 +11,17 @@ export default ObjectController.extend(CanCheckEmails, {
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'),
custom_groups: Ember.computed.filter("model.groups", function(g){
return (!g.automatic && g.visible);
}),
automaticGroups: function() {
return this.get("model.automaticGroups").map((g) => g.name).join(", ");
}.property("model.automaticGroups"),
userFields: function() {
var siteUserFields = this.site.get('user_fields'),
const siteUserFields = this.site.get('user_fields'),
userFields = this.get('user_fields');
if (!Ember.isEmpty(siteUserFields)) {
return siteUserFields.map(function(uf) {
var value = userFields ? userFields[uf.get('id').toString()] : null;
let value = userFields ? userFields[uf.get('id').toString()] : null;
return { name: uf.get('name'), value: value };
});
}
@ -29,40 +29,43 @@ export default ObjectController.extend(CanCheckEmails, {
}.property('user_fields.@each'),
actions: {
toggleTitleEdit: function() {
toggleTitleEdit() {
this.toggleProperty('editingTitle');
},
saveTitle: function() {
Discourse.ajax("/users/" + this.get('username').toLowerCase(), {
saveTitle() {
const self = this;
return Discourse.ajax("/users/" + this.get('username').toLowerCase(), {
data: {title: this.get('title')},
type: 'PUT'
}).then(null, function(e){
}).catch(function(e) {
bootbox.alert(I18n.t("generic_error_with_reason", {error: "http: " + e.status + " - " + e.body}));
}).finally(function() {
self.send('toggleTitleEdit');
});
this.send('toggleTitleEdit');
},
generateApiKey: function() {
generateApiKey() {
this.get('model').generateApiKey();
},
groupAdded: function(added){
groupAdded(added) {
this.get('model').groupAdded(added).catch(function() {
bootbox.alert(I18n.t('generic_error'));
});
},
groupRemoved: function(removed){
this.get('model').groupRemoved(removed).catch(function() {
groupRemoved(groupId) {
this.get('model').groupRemoved(groupId).catch(function() {
bootbox.alert(I18n.t('generic_error'));
});
},
savePrimaryGroup: function() {
var self = this;
Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
savePrimaryGroup() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
type: 'PUT',
data: {primary_group_id: this.get('primary_group_id')}
}).then(function () {
@ -72,26 +75,42 @@ export default ObjectController.extend(CanCheckEmails, {
});
},
resetPrimaryGroup: function() {
resetPrimaryGroup() {
this.set('primary_group_id', this.get('originalPrimaryGroupId'));
},
regenerateApiKey: function() {
var self = this;
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
self.get('model').generateApiKey();
regenerateApiKey() {
const self = this;
bootbox.confirm(
I18n.t("admin.api.confirm_regen"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(result) {
if (result) { self.get('model').generateApiKey(); }
}
});
);
},
revokeApiKey: function() {
var self = this;
bootbox.confirm(I18n.t("admin.api.confirm_revoke"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
self.get('model').revokeApiKey();
revokeApiKey() {
const self = this;
bootbox.confirm(
I18n.t("admin.api.confirm_revoke"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(result) {
if (result) { self.get('model').revokeApiKey(); }
}
});
);
},
anonymize() {
this.get('model').anonymize();
},
destroy() {
this.get('model').destroy();
}
}

View File

@ -2,7 +2,6 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import Controller from 'discourse/controllers/controller';
export default Controller.extend(ModalFunctionality, {
needs: ["adminBackupsLogs"],
_startBackup: function (withUploads) {
@ -17,11 +16,11 @@ export default Controller.extend(ModalFunctionality, {
actions: {
startBackup: function () {
return this._startBackup();
this._startBackup();
},
startBackupWithoutUpload: function () {
return this._startBackup(false);
this._startBackup(false);
},
cancel: function () {

View File

@ -1,5 +1,4 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
@ -19,7 +18,7 @@ export default ObjectController.extend(ModalFunctionality, {
window.location.reload();
}, function(e) {
var error = I18n.t('admin.user.suspend_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error, function() { self.send('showModal'); });
bootbox.alert(error, function() { self.send('reopenModal'); });
});
}
}

View File

@ -1,5 +1,4 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
@ -7,9 +6,10 @@ export default ObjectController.extend(ModalFunctionality, {
newSelected: Ember.computed.equal('selectedTab', 'new'),
onShow: function() {
this.selectNew();
this.send("selectNew");
},
actions: {
selectNew: function() {
this.set('selectedTab', 'new');
},
@ -17,4 +17,5 @@ export default ObjectController.extend(ModalFunctionality, {
selectPrevious: function() {
this.set('selectedTab', 'previous');
}
}
});

View File

@ -1,7 +1,7 @@
import ChangeSiteCustomizationDetailsController from "admin/controllers/change-site-customization-details";
import ChangeSiteCustomizationDetailsController from "admin/controllers/modals/change-site-customization-details";
export default ChangeSiteCustomizationDetailsController.extend({
onShow: function() {
this.selectPrevious();
this.send("selectPrevious");
}
});

View File

@ -0,0 +1,545 @@
const AdminUser = Discourse.User.extend({
customGroups: Em.computed.filter("groups", (g) => !g.automatic && Discourse.Group.create(g)),
automaticGroups: Em.computed.filter("groups", (g) => g.automatic && Discourse.Group.create(g)),
generateApiKey() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/generate_api_key", {
type: 'POST'
}).then(function (result) {
const apiKey = Discourse.ApiKey.create(result.api_key);
self.set('api_key', apiKey);
return apiKey;
});
},
groupAdded(added) {
return Discourse.ajax("/admin/users/" + this.get('id') + "/groups", {
type: 'POST',
data: { group_id: added.id }
}).then(() => this.get('groups').pushObject(added));
},
groupRemoved(groupId) {
return Discourse.ajax("/admin/users/" + this.get('id') + "/groups/" + groupId, {
type: 'DELETE'
}).then(() => this.set('groups.[]', this.get('groups').rejectBy("id", groupId)));
},
revokeApiKey() {
return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_api_key", {
type: 'DELETE'
}).then(() => this.set('api_key', null));
},
deleteAllPostsExplanation: function() {
if (!this.get('can_delete_all_posts')) {
if (this.get('deleteForbidden') && this.get('staff')) {
return I18n.t('admin.user.delete_posts_forbidden_because_staff');
}
if (this.get('post_count') > Discourse.SiteSettings.delete_all_posts_max) {
return I18n.t('admin.user.cant_delete_all_too_many_posts', {count: Discourse.SiteSettings.delete_all_posts_max});
} else {
return I18n.t('admin.user.cant_delete_all_posts', {count: Discourse.SiteSettings.delete_user_max_post_age});
}
} else {
return null;
}
}.property('can_delete_all_posts', 'deleteForbidden'),
deleteAllPosts() {
const user = this,
message = I18n.t('admin.user.delete_all_posts_confirm', { posts: user.get('post_count'), topics: user.get('topic_count') }),
buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel-inline",
"link": true
}, {
"label": '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("admin.user.delete_all_posts"),
"class": "btn btn-danger",
"callback": function() {
Discourse.ajax("/admin/users/" + user.get('id') + "/delete_all_posts", {
type: 'PUT'
}).then(() => user.set('post_count', 0));
}
}];
bootbox.dialog(message, buttons, { "classes": "delete-all-posts" });
},
revokeAdmin() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_admin", {
type: 'PUT'
}).then(function() {
self.setProperties({
admin: false,
can_grant_admin: true,
can_revoke_admin: false
});
});
},
grantAdmin() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/grant_admin", {
type: 'PUT'
}).then(function() {
self.setProperties({
admin: true,
can_grant_admin: false,
can_revoke_admin: true
});
}).catch(function(e) {
let error;
if (e.responseJSON && e.responseJSON.error) {
error = e.responseJSON.error;
}
error = error || I18n.t('admin.user.grant_admin_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
revokeModeration() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_moderation", {
type: 'PUT'
}).then(function() {
self.setProperties({
moderator: false,
can_grant_moderation: true,
can_revoke_moderation: false
});
});
},
grantModeration() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/grant_moderation", {
type: 'PUT'
}).then(function() {
self.setProperties({
moderator: true,
can_grant_moderation: false,
can_revoke_moderation: true
});
}).catch(function(e) {
let error;
if (e.responseJSON && e.responseJSON.error) {
error = e.responseJSON.error;
}
error = error || I18n.t('admin.user.grant_moderation_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
refreshBrowsers() {
return Discourse.ajax("/admin/users/" + this.get('id') + "/refresh_browsers", {
type: 'POST'
}).finally(() => bootbox.alert(I18n.t("admin.user.refresh_browsers_message")));
},
approve() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/approve", {
type: 'PUT'
}).then(function() {
self.setProperties({
can_approve: false,
approved: true,
approved_by: Discourse.User.current()
});
});
},
setOriginalTrustLevel() {
this.set('originalTrustLevel', this.get('trust_level'));
},
trustLevels: function() {
return Discourse.Site.currentProp('trustLevels');
}.property(),
dirty: Discourse.computed.propertyNotEqual('originalTrustLevel', 'trustLevel.id'),
saveTrustLevel() {
return Discourse.ajax("/admin/users/" + this.id + "/trust_level", {
type: 'PUT',
data: { level: this.get('trustLevel.id') }
}).then(function() {
window.location.reload();
}).catch(function(e) {
let error;
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
}
error = error || I18n.t('admin.user.trust_level_change_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
restoreTrustLevel() {
this.set('trustLevel.id', this.get('originalTrustLevel'));
},
lockTrustLevel(locked) {
return Discourse.ajax("/admin/users/" + this.id + "/trust_level_lock", {
type: 'PUT',
data: { locked: !!locked }
}).then(function() {
window.location.reload();
}).catch(function(e) {
let error;
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
}
error = error || I18n.t('admin.user.trust_level_change_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
canLockTrustLevel: function() {
return this.get('trust_level') < 4;
}.property('trust_level'),
isSuspended: Em.computed.equal('suspended', true),
canSuspend: Em.computed.not('staff'),
suspendDuration: function() {
const suspended_at = moment(this.suspended_at),
suspended_till = moment(this.suspended_till);
return suspended_at.format('L') + " - " + suspended_till.format('L');
}.property('suspended_till', 'suspended_at'),
suspend(duration, reason) {
return Discourse.ajax("/admin/users/" + this.id + "/suspend", {
type: 'PUT',
data: { duration: duration, reason: reason }
});
},
unsuspend() {
return Discourse.ajax("/admin/users/" + this.id + "/unsuspend", {
type: 'PUT'
}).then(function() {
window.location.reload();
}).catch(function(e) {
var error = I18n.t('admin.user.unsuspend_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
log_out() {
return Discourse.ajax("/admin/users/" + this.id + "/log_out", {
type: 'POST',
data: { username_or_email: this.get('username') }
}).then(function() {
bootbox.alert(I18n.t("admin.user.logged_out"));
});
},
impersonate() {
return Discourse.ajax("/admin/impersonate", {
type: 'POST',
data: { username_or_email: this.get('username') }
}).then(function() {
document.location = "/";
}).catch(function(e) {
if (e.status === 404) {
bootbox.alert(I18n.t('admin.impersonate.not_found'));
} else {
bootbox.alert(I18n.t('admin.impersonate.invalid'));
}
});
},
activate() {
return Discourse.ajax('/admin/users/' + this.id + '/activate', {
type: 'PUT'
}).then(function() {
window.location.reload();
}).catch(function(e) {
var error = I18n.t('admin.user.activate_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
deactivate() {
return Discourse.ajax('/admin/users/' + this.id + '/deactivate', {
type: 'PUT'
}).then(function() {
window.location.reload();
}).catch(function(e) {
var error = I18n.t('admin.user.deactivate_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
unblock() {
return Discourse.ajax('/admin/users/' + this.id + '/unblock', {
type: 'PUT'
}).then(function() {
window.location.reload();
}).catch(function(e) {
var error = I18n.t('admin.user.unblock_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
block() {
return Discourse.ajax('/admin/users/' + this.id + '/block', {
type: 'PUT'
}).then(function() {
window.location.reload();
}).catch(function(e) {
var error = I18n.t('admin.user.block_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
sendActivationEmail() {
return Discourse.ajax('/users/action/send_activation_email', {
type: 'POST',
data: { username: this.get('username') }
}).then(function() {
bootbox.alert( I18n.t('admin.user.activation_email_sent') );
}).catch(function(e) {
var error = I18n.t('admin.user.send_activation_email_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
anonymizeForbidden: Em.computed.not("can_be_anonymized"),
anonymize() {
const user = this,
message = I18n.t("admin.user.anonymize_confirm");
const performAnonymize = function() {
return Discourse.ajax("/admin/users/" + user.get('id') + '/anonymize.json', {
type: 'PUT'
}).then(function(data) {
if (data.success) {
if (data.username) {
document.location = "/admin/users/" + data.username;
} else {
document.location = "/admin/users/list/active";
}
} else {
bootbox.alert(I18n.t("admin.user.anonymize_failed"));
if (data.user) {
user.setProperties(data.user);
}
}
}).catch(function() {
bootbox.alert(I18n.t("admin.user.anonymize_failed"));
});
};
const buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel",
"link": true
}, {
"label": '<i class="fa fa-exclamation-triangle"></i>' + I18n.t('admin.user.anonymize_yes'),
"class": "btn btn-danger",
"callback": function() { performAnonymize(); }
}];
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
},
deleteForbidden: Em.computed.not("canBeDeleted"),
deleteExplanation: function() {
if (this.get('deleteForbidden')) {
if (this.get('staff')) {
return I18n.t('admin.user.delete_forbidden_because_staff');
} else {
return I18n.t('admin.user.delete_forbidden', {count: Discourse.SiteSettings.delete_user_max_post_age});
}
} else {
return null;
}
}.property('deleteForbidden'),
destroy(opts) {
const user = this,
message = I18n.t("admin.user.delete_confirm"),
location = document.location.pathname;
const performDestroy = function(block) {
let formData = { context: location };
if (block) {
formData["block_email"] = true;
formData["block_urls"] = true;
formData["block_ip"] = true;
}
if (opts && opts.deletePosts) {
formData["delete_posts"] = true;
}
return Discourse.ajax("/admin/users/" + user.get('id') + '.json', {
type: 'DELETE',
data: formData
}).then(function(data) {
if (data.deleted) {
if (/^\/admin\/users\/list\//.test(location)) {
document.location = location;
} else {
document.location = "/admin/users/list/active";
}
} else {
bootbox.alert(I18n.t("admin.user.delete_failed"));
if (data.user) {
user.setProperties(data.user);
}
}
}).catch(function() {
Discourse.AdminUser.find( user.get('username') ).then(function(u){ user.setProperties(u); });
bootbox.alert(I18n.t("admin.user.delete_failed"));
});
};
const buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel",
"link": true
}, {
"label": I18n.t('admin.user.delete_dont_block'),
"class": "btn",
"callback": function(){ performDestroy(false); }
}, {
"label": '<i class="fa fa-exclamation-triangle"></i>' + I18n.t('admin.user.delete_and_block'),
"class": "btn btn-danger",
"callback": function(){ performDestroy(true); }
}];
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
},
deleteAsSpammer(successCallback) {
const user = this;
user.checkEmail().then(function() {
const data = {
posts: user.get('post_count'),
topics: user.get('topic_count'),
email: user.get('email') || I18n.t("flagging.hidden_email_address"),
ip_address: user.get('ip_address') || I18n.t("flagging.ip_address_missing")
};
const message = I18n.t('flagging.delete_confirm', data),
buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel-inline",
"link": true
}, {
"label": '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("flagging.yes_delete_spammer"),
"class": "btn btn-danger",
"callback": function() {
return Discourse.ajax("/admin/users/" + user.get('id') + '.json', {
type: 'DELETE',
data: {
delete_posts: true,
block_email: true,
block_urls: true,
block_ip: true,
delete_as_spammer: true,
context: window.location.pathname
}
}).then(function(result) {
if (result.deleted) {
if (successCallback) successCallback();
} else {
bootbox.alert(I18n.t("admin.user.delete_failed"));
}
}).catch(function() {
bootbox.alert(I18n.t("admin.user.delete_failed"));
});
}
}];
bootbox.dialog(message, buttons, {"classes": "flagging-delete-spammer"});
});
},
loadDetails() {
const user = this;
if (user.get('loadedDetails')) { return Ember.RSVP.resolve(user); }
return Discourse.AdminUser.find(user.get('username_lower')).then(function (result) {
user.setProperties(result);
user.set('loadedDetails', true);
});
},
tl3Requirements: function() {
if (this.get('tl3_requirements')) {
return Discourse.TL3Requirements.create(this.get('tl3_requirements'));
}
}.property('tl3_requirements'),
suspendedBy: function() {
if (this.get('suspended_by')) {
return Discourse.AdminUser.create(this.get('suspended_by'));
}
}.property('suspended_by'),
approvedBy: function() {
if (this.get('approved_by')) {
return Discourse.AdminUser.create(this.get('approved_by'));
}
}.property('approved_by')
});
AdminUser.reopenClass({
bulkApprove(users) {
_.each(users, function(user) {
user.setProperties({
approved: true,
can_approve: false,
selected: false
});
});
return Discourse.ajax("/admin/users/approve-bulk", {
type: 'PUT',
data: { users: users.map((u) => u.id) }
}).finally(() => bootbox.alert(I18n.t("admin.user.approve_bulk_success")));
},
bulkReject(users) {
_.each(users, function(user) {
user.set('can_approve', false);
user.set('selected', false);
});
return Discourse.ajax("/admin/users/reject-bulk", {
type: 'DELETE',
data: {
users: users.map((u) => u.id),
context: window.location.pathname
}
});
},
find(username) {
return Discourse.ajax("/admin/users/" + username + ".json").then(function (result) {
result.loadedDetails = true;
return Discourse.AdminUser.create(result);
});
},
findAll(query, filter) {
return Discourse.ajax("/admin/users/list/" + query + ".json", {
data: filter
}).then(function(users) {
return users.map((u) => Discourse.AdminUser.create(u));
});
}
});
export default AdminUser;

View File

@ -1,501 +0,0 @@
/**
Our data model for dealing with users from the admin section.
@class AdminUser
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.AdminUser = Discourse.User.extend({
/**
Generates an API key for the user. Will regenerate if they already have one.
@method generateApiKey
@returns {Promise} a promise that resolves to the newly generated API key
**/
generateApiKey: function() {
var self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/generate_api_key", {type: 'POST'}).then(function (result) {
var apiKey = Discourse.ApiKey.create(result.api_key);
self.set('api_key', apiKey);
return apiKey;
});
},
groupAdded: function(added){
var self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/groups", {
type: 'POST',
data: {group_id: added.id}
}).then(function () {
self.get('groups').pushObject(added);
});
},
groupRemoved: function(removed){
var self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/groups/" + removed.id, {
type: 'DELETE'
}).then(function () {
self.set('groups.[]', self.get('groups').rejectBy("id", removed.id));
});
},
/**
Revokes a user's current API key
@method revokeApiKey
@returns {Promise} a promise that resolves when the API key has been deleted
**/
revokeApiKey: function() {
var self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_api_key", {type: 'DELETE'}).then(function () {
self.set('api_key', null);
});
},
deleteAllPostsExplanation: function() {
if (!this.get('can_delete_all_posts')) {
if (this.get('post_count') > Discourse.SiteSettings.delete_all_posts_max) {
return I18n.t('admin.user.cant_delete_all_too_many_posts', {count: Discourse.SiteSettings.delete_all_posts_max});
} else {
return I18n.t('admin.user.cant_delete_all_posts', {count: Discourse.SiteSettings.delete_user_max_post_age});
}
} else {
return null;
}
}.property('can_delete_all_posts'),
deleteAllPosts: function() {
var user = this;
var message = I18n.t('admin.user.delete_all_posts_confirm', {posts: user.get('post_count'), topics: user.get('topic_count')});
var buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel-inline",
"link": true
}, {
"label": '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("admin.user.delete_all_posts"),
"class": "btn btn-danger",
"callback": function() {
Discourse.ajax("/admin/users/" + (user.get('id')) + "/delete_all_posts", {type: 'PUT'}).then(function(){
user.set('post_count', 0);
});
}
}];
bootbox.dialog(message, buttons, {"classes": "delete-all-posts"});
},
// Revoke the user's admin access
revokeAdmin: function() {
this.set('admin', false);
this.set('can_grant_admin', true);
this.set('can_revoke_admin', false);
return Discourse.ajax("/admin/users/" + (this.get('id')) + "/revoke_admin", {type: 'PUT'});
},
grantAdmin: function() {
this.set('admin', true);
this.set('can_grant_admin', false);
this.set('can_revoke_admin', true);
Discourse.ajax("/admin/users/" + (this.get('id')) + "/grant_admin", {type: 'PUT'});
},
// Revoke the user's moderation access
revokeModeration: function() {
this.set('moderator', false);
this.set('can_grant_moderation', true);
this.set('can_revoke_moderation', false);
return Discourse.ajax("/admin/users/" + (this.get('id')) + "/revoke_moderation", {type: 'PUT'});
},
grantModeration: function() {
this.set('moderator', true);
this.set('can_grant_moderation', false);
this.set('can_revoke_moderation', true);
Discourse.ajax("/admin/users/" + (this.get('id')) + "/grant_moderation", {type: 'PUT'});
},
refreshBrowsers: function() {
Discourse.ajax("/admin/users/" + (this.get('id')) + "/refresh_browsers", {type: 'POST'});
bootbox.alert(I18n.t("admin.user.refresh_browsers_message"));
},
approve: function() {
this.set('can_approve', false);
this.set('approved', true);
this.set('approved_by', Discourse.User.current());
Discourse.ajax("/admin/users/" + (this.get('id')) + "/approve", {type: 'PUT'});
},
username_lower: (function() {
return this.get('username').toLowerCase();
}).property('username'),
setOriginalTrustLevel: function() {
this.set('originalTrustLevel', this.get('trust_level'));
},
trustLevels: function() {
return Discourse.Site.currentProp('trustLevels');
}.property(),
dirty: Discourse.computed.propertyNotEqual('originalTrustLevel', 'trustLevel.id'),
saveTrustLevel: function() {
Discourse.ajax("/admin/users/" + this.id + "/trust_level", {
type: 'PUT',
data: {level: this.get('trustLevel.id')}
}).then(function () {
// succeeded
window.location.reload();
}, function(e) {
// failure
var error;
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
}
error = error || I18n.t('admin.user.trust_level_change_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
restoreTrustLevel: function() {
this.set('trustLevel.id', this.get('originalTrustLevel'));
},
lockTrustLevel: function(locked) {
Discourse.ajax("/admin/users/" + this.id + "/trust_level_lock", {
type: 'PUT',
data: { locked: !!locked }
}).then(function() {
// succeeded
window.location.reload();
}, function(e) {
// failure
var error;
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
}
error = error || I18n.t('admin.user.trust_level_change_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
canLockTrustLevel: function(){
return this.get('trust_level') < 4;
}.property('trust_level'),
isSuspended: Em.computed.equal('suspended', true),
canSuspend: Em.computed.not('staff'),
suspendDuration: function() {
var suspended_at = moment(this.suspended_at);
var suspended_till = moment(this.suspended_till);
return suspended_at.format('L') + " - " + suspended_till.format('L');
}.property('suspended_till', 'suspended_at'),
suspend: function(duration, reason) {
return Discourse.ajax("/admin/users/" + this.id + "/suspend", {
type: 'PUT',
data: {duration: duration, reason: reason}
});
},
unsuspend: function() {
Discourse.ajax("/admin/users/" + this.id + "/unsuspend", {
type: 'PUT'
}).then(function() {
// succeeded
window.location.reload();
}, function(e) {
// failed
var error = I18n.t('admin.user.unsuspend_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
log_out: function(){
Discourse.ajax("/admin/users/" + this.id + "/log_out", {
type: 'POST',
data: { username_or_email: this.get('username') }
}).then(
function(){
bootbox.alert(I18n.t("admin.user.logged_out"));
}
);
},
impersonate: function() {
Discourse.ajax("/admin/impersonate", {
type: 'POST',
data: { username_or_email: this.get('username') }
}).then(function() {
// succeeded
document.location = "/";
}, function(e) {
// failed
if (e.status === 404) {
bootbox.alert(I18n.t('admin.impersonate.not_found'));
} else {
bootbox.alert(I18n.t('admin.impersonate.invalid'));
}
});
},
activate: function() {
Discourse.ajax('/admin/users/' + this.id + '/activate', {type: 'PUT'}).then(function() {
// succeeded
window.location.reload();
}, function(e) {
// failed
var error = I18n.t('admin.user.activate_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
deactivate: function() {
Discourse.ajax('/admin/users/' + this.id + '/deactivate', {type: 'PUT'}).then(function() {
// succeeded
window.location.reload();
}, function(e) {
// failed
var error = I18n.t('admin.user.deactivate_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
unblock: function() {
Discourse.ajax('/admin/users/' + this.id + '/unblock', {type: 'PUT'}).then(function() {
// succeeded
window.location.reload();
}, function(e) {
// failed
var error = I18n.t('admin.user.unblock_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
block: function() {
Discourse.ajax('/admin/users/' + this.id + '/block', {type: 'PUT'}).then(function() {
// succeeded
window.location.reload();
}, function(e) {
// failed
var error = I18n.t('admin.user.block_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
sendActivationEmail: function() {
Discourse.ajax('/users/action/send_activation_email', {data: {username: this.get('username')}, type: 'POST'}).then(function() {
// succeeded
bootbox.alert( I18n.t('admin.user.activation_email_sent') );
}, function(e) {
// failed
var error = I18n.t('admin.user.send_activation_email_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
deleteForbidden: Em.computed.not("canBeDeleted"),
deleteExplanation: function() {
if (this.get('deleteForbidden')) {
if (this.get('staff')) {
return I18n.t('admin.user.delete_forbidden_because_staff');
} else {
return I18n.t('admin.user.delete_forbidden', {count: Discourse.SiteSettings.delete_user_max_post_age});
}
} else {
return null;
}
}.property('deleteForbidden'),
destroy: function(opts) {
var user = this;
var location = document.location.pathname;
var performDestroy = function(block) {
var formData = { context: location };
if (block) {
formData["block_email"] = true;
formData["block_urls"] = true;
formData["block_ip"] = true;
}
if (opts && opts.deletePosts) {
formData["delete_posts"] = true;
}
Discourse.ajax("/admin/users/" + user.get('id') + '.json', {
type: 'DELETE',
data: formData
}).then(function(data) {
if (data.deleted) {
if (/^\/admin\/users\/list\//.test(location)) {
document.location = location;
} else {
document.location = "/admin/users/list/active";
}
} else {
bootbox.alert(I18n.t("admin.user.delete_failed"));
if (data.user) {
user.setProperties(data.user);
}
}
}, function() {
Discourse.AdminUser.find( user.get('username') ).then(function(u){ user.setProperties(u); });
bootbox.alert(I18n.t("admin.user.delete_failed"));
});
};
var message = I18n.t("admin.user.delete_confirm");
var buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel",
"link": true
}, {
"label": I18n.t('admin.user.delete_dont_block'),
"class": "btn",
"callback": function(){
performDestroy(false);
}
}, {
"label": '<i class="fa fa-exclamation-triangle"></i>' + I18n.t('admin.user.delete_and_block'),
"class": "btn btn-danger",
"callback": function(){
performDestroy(true);
}
}];
bootbox.dialog(message, buttons, {"classes": "delete-user-modal"});
},
deleteAsSpammer: function(successCallback) {
var user = this;
user.checkEmail().then(function() {
var data = {
posts: user.get('post_count'),
topics: user.get('topic_count'),
email: user.get('email') || I18n.t("flagging.hidden_email_address"),
ip_address: user.get('ip_address') || I18n.t("flagging.ip_address_missing")
};
var message = I18n.t('flagging.delete_confirm', data);
var buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel-inline",
"link": true
}, {
"label": '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("flagging.yes_delete_spammer"),
"class": "btn btn-danger",
"callback": function() {
Discourse.ajax("/admin/users/" + user.get('id') + '.json', {
type: 'DELETE',
data: {
delete_posts: true,
block_email: true,
block_urls: true,
block_ip: true,
delete_as_spammer: true,
context: window.location.pathname
}
}).then(function(result) {
if (result.deleted) {
if (successCallback) successCallback();
} else {
bootbox.alert(I18n.t("admin.user.delete_failed"));
}
}, function() {
bootbox.alert(I18n.t("admin.user.delete_failed"));
});
}
}];
bootbox.dialog(message, buttons, {"classes": "flagging-delete-spammer"});
});
},
loadDetails: function() {
var model = this;
if (model.get('loadedDetails')) { return Ember.RSVP.resolve(model); }
return Discourse.AdminUser.find(model.get('username_lower')).then(function (result) {
model.setProperties(result);
model.set('loadedDetails', true);
});
},
tl3Requirements: function() {
if (this.get('tl3_requirements')) {
return Discourse.TL3Requirements.create(this.get('tl3_requirements'));
}
}.property('tl3_requirements'),
suspendedBy: function() {
if (this.get('suspended_by')) {
return Discourse.AdminUser.create(this.get('suspended_by'));
}
}.property('suspended_by'),
approvedBy: function() {
if (this.get('approved_by')) {
return Discourse.AdminUser.create(this.get('approved_by'));
}
}.property('approved_by')
});
Discourse.AdminUser.reopenClass({
bulkApprove: function(users) {
_.each(users, function(user) {
user.set('approved', true);
user.set('can_approve', false);
return user.set('selected', false);
});
bootbox.alert(I18n.t("admin.user.approve_bulk_success"));
return Discourse.ajax("/admin/users/approve-bulk", {
type: 'PUT',
data: {
users: users.map(function(u) {
return u.id;
})
}
});
},
bulkReject: function(users) {
_.each(users, function(user){
user.set('can_approve', false);
user.set('selected', false);
});
return Discourse.ajax("/admin/users/reject-bulk", {
type: 'DELETE',
data: {
users: users.map(function(u) { return u.id; }),
context: window.location.pathname
}
});
},
find: function(username) {
return Discourse.ajax("/admin/users/" + username + ".json").then(function (result) {
result.loadedDetails = true;
return Discourse.AdminUser.create(result);
});
},
findAll: function(query, filter) {
return Discourse.ajax("/admin/users/list/" + query + ".json", {
data: filter
}).then(function(users) {
return users.map(function(u) {
return Discourse.AdminUser.create(u);
});
});
}
});

View File

@ -92,9 +92,9 @@ Discourse.Report = Discourse.Model.extend({
icon: function() {
switch( this.get('type') ) {
case 'flags':
return 'fa-flag';
return 'flag';
case 'likes':
return 'fa-heart';
return 'heart';
default:
return null;
}

View File

@ -1,11 +1,6 @@
/**
Represents an IP address that is watched for during account registration
(and possibly other times), and an action is taken.
@class ScreenedIpAddress
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.ScreenedIpAddress = Discourse.Model.extend({
actionName: function() {
@ -17,21 +12,9 @@ Discourse.ScreenedIpAddress = Discourse.Model.extend({
}.property('action_name'),
actionIcon: function() {
if (this.get('action_name') === 'block') {
return this.get('blockIcon');
} else {
return this.get('doNothingIcon');
}
return (this.get('action_name') === 'block') ? 'ban' : 'check';
}.property('action_name'),
blockIcon: function() {
return 'fa-ban';
}.property(),
doNothingIcon: function() {
return 'fa-check';
}.property(),
save: function() {
return Discourse.ajax("/admin/logs/screened_ip_addresses" + (this.id ? '/' + this.id : '') + ".json", {
type: this.id ? 'PUT' : 'POST',
@ -45,8 +28,8 @@ Discourse.ScreenedIpAddress = Discourse.Model.extend({
});
Discourse.ScreenedIpAddress.reopenClass({
findAll: function() {
return Discourse.ajax("/admin/logs/screened_ip_addresses.json").then(function(screened_ips) {
findAll: function(filter) {
return Discourse.ajax("/admin/logs/screened_ip_addresses.json", { data: { filter: filter } }).then(function(screened_ips) {
return screened_ips.map(function(b) {
return Discourse.ScreenedIpAddress.create(b);
});

View File

@ -0,0 +1,54 @@
const SiteSetting = Discourse.Model.extend({
overridden: function() {
let val = this.get('value'),
defaultVal = this.get('default');
if (val === null) val = '';
if (defaultVal === null) defaultVal = '';
return val.toString() !== defaultVal.toString();
}.property('value', 'default'),
validValues: function() {
const vals = [],
translateNames = this.get('translate_names');
this.get('valid_values').forEach(function(v) {
if (v.name && v.name.length > 0) {
vals.addObject(translateNames ? {name: I18n.t(v.name), value: v.value} : v);
}
});
return vals;
}.property('valid_values'),
allowsNone: function() {
if ( _.indexOf(this.get('valid_values'), '') >= 0 ) return 'admin.site_settings.none';
}.property('valid_values')
});
SiteSetting.reopenClass({
findAll() {
return Discourse.ajax("/admin/site_settings").then(function (settings) {
// Group the results by category
const categories = {};
settings.site_settings.forEach(function(s) {
if (!categories[s.category]) {
categories[s.category] = [];
}
categories[s.category].pushObject(Discourse.SiteSetting.create(s));
});
return Object.keys(categories).map(function(n) {
return {nameKey: n, name: I18n.t('admin.site_settings.categories.' + n), siteSettings: categories[n]};
});
});
},
update(key, value) {
const data = {};
data[key] = value;
return Discourse.ajax("/admin/site_settings/" + key, { type: 'PUT', data });
}
});
export default SiteSetting;

View File

@ -1,136 +0,0 @@
Discourse.SiteSetting = Discourse.Model.extend({
validationMessage: null,
/**
Is the boolean setting true?
@property enabled
**/
enabled: function(key, value) {
if (arguments.length > 1) {
this.set('value', value ? 'true' : 'false');
}
if (this.blank('value')) return false;
return this.get('value') === 'true';
}.property('value'),
/**
The name of the setting. Basically, underscores in the setting key are replaced with spaces.
@property settingName
**/
settingName: function() {
return this.get('setting').replace(/\_/g, ' ');
}.property('setting'),
/**
Has the user changed the setting? If so we should save it.
@property dirty
**/
dirty: function() {
return this.get('originalValue') !== this.get('value');
}.property('originalValue', 'value'),
overridden: function() {
var val = this.get('value'),
defaultVal = this.get('default');
if (val === null) val = '';
if (defaultVal === null) defaultVal = '';
return val.toString() !== defaultVal.toString();
}.property('value', 'default'),
/**
Reset the setting to its original value.
@method resetValue
**/
resetValue: function() {
this.set('value', this.get('originalValue'));
this.set('validationMessage', null);
},
/**
Save the setting's value.
@method save
**/
save: function() {
// Update the setting
var self = this, data = {};
data[this.get('setting')] = this.get('value');
return Discourse.ajax("/admin/site_settings/" + this.get('setting'), {
data: data,
type: 'PUT'
}).then(function() {
self.set('originalValue', self.get('value'));
self.set('validationMessage', null);
}, function(e) {
if (e.responseJSON && e.responseJSON.errors) {
self.set('validationMessage', e.responseJSON.errors[0]);
} else {
self.set('validationMessage', I18n.t('generic_error'));
}
});
},
validValues: function() {
var vals, setting;
vals = Em.A();
setting = this;
_.each(this.get('valid_values'), function(v) {
if (v.name && v.name.length > 0) {
if (setting.translate_names) {
vals.addObject({name: I18n.t(v.name), value: v.value});
} else {
vals.addObject(v);
}
}
});
return vals;
}.property('valid_values'),
allowsNone: function() {
if ( _.indexOf(this.get('valid_values'), '') >= 0 ) return 'admin.site_settings.none';
}.property('valid_values')
});
Discourse.SiteSetting.reopenClass({
findAll: function() {
return Discourse.ajax("/admin/site_settings").then(function (settings) {
// Group the results by category
var categoryNames = [],
categories = {},
result = Em.A();
_.each(settings.site_settings,function(s) {
s.originalValue = s.value;
if (!categoryNames.contains(s.category)) {
categoryNames.pushObject(s.category);
categories[s.category] = Em.A();
}
categories[s.category].pushObject(Discourse.SiteSetting.create(s));
});
_.each(categoryNames, function(n) {
result.pushObject({nameKey: n, name: I18n.t('admin.site_settings.categories.' + n), siteSettings: categories[n]});
});
return result;
});
},
update: function(key, value) {
return Discourse.ajax("/admin/site_settings/" + key, {
type: 'PUT',
data: { value: value }
});
}
});

View File

@ -1,11 +1,3 @@
/**
Represents an action taken by a staff member that has been logged.
@class StaffActionLog
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.StaffActionLog = Discourse.Model.extend({
showFullDetails: false,

View File

@ -1,12 +1,14 @@
Discourse.AdminBackupsRoute = Discourse.Route.extend({
import showModal from 'discourse/lib/show-modal';
LOG_CHANNEL: "/admin/backups/logs",
const LOG_CHANNEL = "/admin/backups/logs";
activate: function() {
Discourse.MessageBus.subscribe(this.LOG_CHANNEL, this._processLogMessage.bind(this));
export default Discourse.Route.extend({
activate() {
this.messageBus.subscribe(LOG_CHANNEL, this._processLogMessage.bind(this));
},
_processLogMessage: function(log) {
_processLogMessage(log) {
if (log.message === "[STARTED]") {
this.controllerFor("adminBackups").set("isOperationRunning", true);
this.controllerFor("adminBackupsLogs").clear();
@ -25,7 +27,7 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
}
},
model: function() {
model() {
return PreloadStore.getAndRemove("operations_status", function() {
return Discourse.ajax("/admin/backups/status.json");
}).then(function (status) {
@ -37,35 +39,24 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
});
},
deactivate: function() {
Discourse.MessageBus.unsubscribe(this.LOG_CHANNEL);
deactivate() {
this.messageBus.unsubscribe(LOG_CHANNEL);
},
actions: {
/**
Starts a backup and redirect the user to the logs tab
@method startBackup
**/
startBackup: function() {
Discourse.Route.showModal(this, 'admin_start_backup');
startBackup() {
showModal('modals/admin-start-backup');
this.controllerFor('modal').set('modalClass', 'start-backup-modal');
},
backupStarted: function () {
backupStarted() {
this.modelFor("adminBackups").set("isOperationRunning", true);
this.transitionTo("admin.backups.logs");
this.send("closeModal");
},
/**
Destroys a backup
@method destroyBackup
@param {Discourse.Backup} backup the backup to destroy
**/
destroyBackup: function(backup) {
var self = this;
destroyBackup(backup) {
const self = this;
bootbox.confirm(
I18n.t("admin.backups.operations.destroy.confirm"),
I18n.t("no_value"),
@ -80,14 +71,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
);
},
/**
Start a restore and redirect the user to the logs tab
@method startRestore
@param {Discourse.Backup} backup the backup to restore
**/
startRestore: function(backup) {
var self = this;
startRestore(backup) {
const self = this;
bootbox.confirm(
I18n.t("admin.backups.operations.restore.confirm"),
I18n.t("no_value"),
@ -105,13 +90,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
);
},
/**
Cancels the current operation
@method cancelOperation
**/
cancelOperation: function() {
var self = this;
cancelOperation() {
const self = this;
bootbox.confirm(
I18n.t("admin.backups.operations.cancel.confirm"),
I18n.t("no_value"),
@ -126,12 +106,7 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
);
},
/**
Rollback to previous working state
@method rollback
**/
rollback: function() {
rollback() {
bootbox.confirm(
I18n.t("admin.backups.operations.rollback.confirm"),
I18n.t("no_value"),
@ -142,8 +117,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
);
},
uploadSuccess: function(filename) {
var self = this;
uploadSuccess(filename) {
const self = this;
bootbox.alert(I18n.t("admin.backups.upload.success", { filename: filename }), function() {
Discourse.Backup.find().then(function (backups) {
self.controllerFor("adminBackupsIndex").set("model", backups);
@ -151,7 +126,7 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
});
},
uploadError: function(filename, message) {
uploadError(filename, message) {
bootbox.alert(I18n.t("admin.backups.upload.error", { filename: filename, message: message }));
}
}

View File

@ -1,9 +1,11 @@
import showModal from 'discourse/lib/show-modal';
export default Ember.Route.extend({
serialize: function(m) {
serialize(m) {
return {badge_id: Em.get(m, 'id') || 'new'};
},
model: function(params) {
model(params) {
if (params.badge_id === "new") {
return Discourse.Badge.create({
name: I18n.t('admin.badges.new_badge')
@ -13,22 +15,20 @@ export default Ember.Route.extend({
},
actions: {
saveError: function(e) {
var msg = I18n.t("generic_error");
saveError(e) {
let msg = I18n.t("generic_error");
if (e.responseJSON && e.responseJSON.errors) {
msg = I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')});
}
bootbox.alert(msg);
},
editGroupings: function() {
var groupings = this.controllerFor('admin-badges').get('badgeGroupings');
Discourse.Route.showModal(this, 'admin_edit_badge_groupings', groupings);
editGroupings() {
const model = this.controllerFor('admin-badges').get('badgeGroupings');
showModal('modals/admin-edit-badge-groupings', { model });
},
preview: function(badge, explain) {
var self = this;
preview(badge, explain) {
badge.set('preview_loading', true);
Discourse.ajax('/admin/badges/preview.json', {
method: 'post',
@ -36,11 +36,11 @@ export default Ember.Route.extend({
sql: badge.get('query'),
target_posts: !!badge.get('target_posts'),
trigger: badge.get('trigger'),
explain: explain
explain
}
}).then(function(json) {
}).then(function(model) {
badge.set('preview_loading', false);
Discourse.Route.showModal(self, 'admin_badge_preview', json);
showModal('modals/admin-badge-preview', { model });
}).catch(function(error) {
badge.set('preview_loading', false);
Em.Logger.error(error);

View File

@ -24,7 +24,8 @@ export default Discourse.Route.extend({
c.set('top_referrers', topReferrers);
}
['admins', 'moderators', 'blocked', 'suspended', 'top_traffic_sources', 'top_referred_topics', 'updated_at'].forEach(function(x) {
[ 'disk_space','admins', 'moderators', 'blocked', 'suspended',
'top_traffic_sources', 'top_referred_topics', 'updated_at'].forEach(function(x) {
c.set(x, d[x]);
});

View File

@ -1,22 +1,24 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({
model: function(params) {
model(params) {
this.filter = params.filter;
return Discourse.FlaggedPost.findAll(params.filter);
},
setupController: function(controller, model) {
setupController(controller, model) {
controller.set('model', model);
controller.set('query', this.filter);
},
actions: {
showAgreeFlagModal: function (flaggedPost) {
Discourse.Route.showModal(this, 'admin_agree_flag', flaggedPost);
showAgreeFlagModal(model) {
showModal('modals/admin-agree-flag', { model });
this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
},
showDeleteFlagModal: function (flaggedPost) {
Discourse.Route.showModal(this, 'admin_delete_flag', flaggedPost);
showDeleteFlagModal(model) {
showModal('modals/admin-delete-flag', { model });
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
}

View File

@ -2,4 +2,4 @@ export default Discourse.Route.extend({
redirect: function() {
this.transitionTo("adminGroupsType", "custom");
}
})
});

View File

@ -1,17 +1,23 @@
export default Discourse.Route.extend({
model: function(params) {
model(params) {
this.set("type", params.type);
return Discourse.Group.findAll().then(function(groups) {
return groups.filterBy("type", params.type);
});
},
setupController(controller, model){
controller.set("type", this.get("type"));
controller.set("model", model);
},
actions: {
newGroup: function() {
var self = this;
newGroup() {
const self = this;
this.transitionTo("adminGroupsType", "custom").then(function() {
var group = Discourse.Group.create({ automatic: false, visible: true });
self.transitionTo("adminGroup", group);
})
});
}
}
});

View File

@ -0,0 +1,26 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({
// TODO: make this automatic using an `{{outlet}}`
renderTemplate: function() {
this.render('admin/templates/logs/staff_action_logs', {into: 'adminLogs'});
},
setupController: function(controller) {
controller.resetFilters();
controller.refresh();
},
actions: {
showDetailsModal(model) {
showModal('modals/admin-staff-action-log-details', { model });
this.controllerFor('modal').set('modalClass', 'log-details-modal');
},
showCustomDetailsModal(model) {
const modalName = "modals/" + (model.action_name + '_details').replace("_", "-");
showModal(modalName, { model });
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
}
}
});

View File

@ -0,0 +1,12 @@
export default Ember.Route.extend({
model() {
return this.store.findAll('plugin');
},
actions: {
showSettings() {
this.transitionTo('adminSiteSettingsCategory', 'plugins');
}
}
});

View File

@ -1,7 +1,7 @@
export default {
resource: 'admin',
map: function() {
map() {
this.route('dashboard', { path: '/' });
this.resource('adminSiteSettings', { path: '/site_settings' }, function() {
this.resource('adminSiteSettingsCategory', { path: 'category/:category_id'} );

View File

@ -1,16 +1,8 @@
/**
Handles routes related to viewing and editing site settings within one category.
@class AdminSiteSettingCategoryRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminSiteSettingsCategoryRoute = Discourse.Route.extend({
model: function(params) {
export default Discourse.Route.extend({
model(params) {
// The model depends on user input, so let the controller do the work:
this.controllerFor('adminSiteSettingsCategory').set('categoryNameKey', params.category_id);
return Em.Object.create({
return Ember.Object.create({
nameKey: params.category_id,
name: I18n.t('admin.site_settings.categories.' + params.category_id),
siteSettings: this.controllerFor('adminSiteSettingsCategory').get('filteredContent')

View File

@ -0,0 +1,9 @@
/**
Handles when you click the Site Settings tab in admin, but haven't
chosen a category. It will redirect to the first category.
**/
export default Discourse.Route.extend({
beforeModel() {
this.replaceWith('adminSiteSettingsCategory', this.modelFor('adminSiteSettings')[0].nameKey);
}
});

View File

@ -0,0 +1,11 @@
import SiteSetting from 'admin/models/site-setting';
export default Discourse.Route.extend({
model() {
return SiteSetting.findAll();
},
afterModel(siteSettings) {
this.controllerFor('adminSiteSettings').set('allSiteSettings', siteSettings);
}
});

View File

@ -0,0 +1,32 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({
model() {
return this.modelFor('adminUser');
},
afterModel(model) {
if (this.currentUser.get('admin')) {
const self = this;
return Discourse.Group.findAll().then(function(groups){
self._availableGroups = groups.filterBy('automatic', false);
return model;
});
}
},
setupController(controller, model) {
controller.setProperties({
originalPrimaryGroupId: model.get('primary_group_id'),
availableGroups: this._availableGroups,
model
});
},
actions: {
showSuspendModal(model) {
showModal('modals/admin-suspend-user', { model });
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
}
}
});

View File

@ -0,0 +1,20 @@
export default Discourse.Route.extend({
serialize(model) {
return { username: model.get('username').toLowerCase() };
},
model(params) {
return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase());
},
renderTemplate() {
this.render({into: 'admin'});
},
afterModel(adminUser) {
return adminUser.loadDetails().then(function () {
adminUser.setOriginalTrustLevel();
return adminUser;
});
}
});

View File

@ -1,5 +1,5 @@
export default Discourse.Route.extend({
redirect: function() {
beforeModel: function() {
this.replaceWith('adminUsersList.show', 'active');
}
});

View File

@ -7,6 +7,10 @@ export default Discourse.Route.extend({
Discourse.ExportCsv.exportUserList().then(outputExportResult);
},
sendInvites: function() {
this.transitionTo('user.invited', Discourse.User.current());
},
deleteUser: function(user) {
Discourse.AdminUser.create(user).destroy({ deletePosts: true });
}

View File

@ -12,47 +12,6 @@ Discourse.AdminLogsIndexRoute = Discourse.Route.extend({
}
});
/**
The route that lists staff actions that were logged.
@class AdminLogsStaffActionLogsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminLogsStaffActionLogsRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/logs/staff_action_logs', {into: 'adminLogs'});
},
setupController: function(controller) {
var queryParams = Discourse.URL.get('queryParams');
if (queryParams) {
controller.set('filters', queryParams);
}
return controller.show();
},
actions: {
showDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, 'admin_staff_action_log_details', logRecord);
this.controllerFor('modal').set('modalClass', 'log-details-modal');
},
showCustomDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, logRecord.action_name + '_details', logRecord);
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
}
},
deactivate: function() {
this._super();
// Clear any filters when we leave the route
Discourse.URL.set('queryParams', null);
}
});
/**
The route that lists blocked email addresses.

View File

@ -1,8 +1,4 @@
Discourse.AdminRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/admin');
},
titleToken: function() {
return I18n.t('admin_title');
}

View File

@ -1,27 +0,0 @@
/**
Handles routes related to viewing and editing site settings.
@class AdminSiteSettingsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminSiteSettingsRoute = Discourse.Route.extend({
model: function() {
return Discourse.SiteSetting.findAll();
},
afterModel: function(siteSettings) {
this.controllerFor('adminSiteSettings').set('allSiteSettings', siteSettings);
}
});
/**
Handles when you click the Site Settings tab in admin, but haven't
chosen a category. It will redirect to the first category.
**/
Discourse.AdminSiteSettingsIndexRoute = Discourse.Route.extend({
model: function() {
this.replaceWith('adminSiteSettingsCategory', this.modelFor('adminSiteSettings')[0].nameKey);
}
});

View File

@ -1,51 +0,0 @@
Discourse.AdminUserRoute = Discourse.Route.extend({
serialize: function(model) {
return { username: model.get('username').toLowerCase() };
},
model: function(params) {
return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase());
},
renderTemplate: function() {
this.render({into: 'admin/templates/admin'});
},
afterModel: function(adminUser) {
return adminUser.loadDetails().then(function () {
adminUser.setOriginalTrustLevel();
return adminUser;
});
}
});
Discourse.AdminUserIndexRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('adminUser');
},
afterModel: function(model) {
if(Discourse.User.currentProp('admin')) {
var self = this;
return Discourse.Group.findAll().then(function(groups){
self._availableGroups = groups.filterBy('automatic', false);
return model;
});
}
},
setupController: function(controller, model) {
controller.setProperties({
originalPrimaryGroupId: model.get('primary_group_id'),
availableGroups: this._availableGroups,
model: model
});
},
actions: {
showSuspendModal: function(user) {
Discourse.Route.showModal(this, 'admin_suspend_user', user);
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
}
}
});

View File

@ -4,25 +4,26 @@
<div class="full-width">
<ul class="nav nav-pills">
<li>{{#link-to 'admin.dashboard'}}{{i18n 'admin.dashboard.title'}}{{/link-to}}</li>
{{admin-nav-item route='admin.dashboard' label='admin.dashboard.title'}}
{{#if currentUser.admin}}
<li>{{#link-to 'adminSiteSettings'}}{{i18n 'admin.site_settings.title'}}{{/link-to}}</li>
{{admin-nav-item route='adminSiteSettings' label='admin.site_settings.title'}}
{{/if}}
<li>{{#link-to 'adminUsersList'}}{{i18n 'admin.users.title'}}{{/link-to}}</li>
{{admin-nav-item route='adminUsersList.show' routeParam='active' label='admin.users.title'}}
{{#if showBadges}}
<li>{{#link-to 'adminBadges.index'}}{{i18n 'admin.badges.title'}}{{/link-to}}</li>
{{admin-nav-item route='adminBadges.index' label='admin.badges.title'}}
{{/if}}
{{#if currentUser.admin}}
<li>{{#link-to 'adminGroups'}}{{i18n 'admin.groups.title'}}{{/link-to}}</li>
{{admin-nav-item route='adminGroups' label='admin.groups.title'}}
{{/if}}
<li>{{#link-to 'adminEmail'}}{{i18n 'admin.email.title'}}{{/link-to}}</li>
<li>{{#link-to 'adminFlags'}}{{i18n 'admin.flags.title'}}{{/link-to}}</li>
<li>{{#link-to 'adminLogs'}}{{i18n 'admin.logs.title'}}{{/link-to}}</li>
{{admin-nav-item route='adminEmail' label='admin.email.title'}}
{{admin-nav-item route='adminFlags' label='admin.flags.title'}}
{{admin-nav-item route='adminLogs' label='admin.logs.title'}}
{{#if currentUser.admin}}
<li>{{#link-to 'adminCustomize.colors'}}{{i18n 'admin.customize.title'}}{{/link-to}}</li>
<li>{{#link-to 'admin.api'}}{{i18n 'admin.api.title'}}{{/link-to}}</li>
<li>{{#link-to 'admin.backups'}}{{i18n 'admin.backups.title'}}{{/link-to}}</li>
{{admin-nav-item route='adminCustomize.colors' label='admin.customize.title'}}
{{admin-nav-item route='admin.api' label='admin.api.title'}}
{{admin-nav-item route='admin.backups' label='admin.backups.title'}}
{{/if}}
{{admin-nav-item route='adminPlugins' label='admin.plugins.title'}}
{{plugin-outlet "admin-menu" tagName="li"}}
</ul>

View File

@ -1,18 +1,31 @@
<div class="admin-controls">
<div class="span15">
<ul class="nav nav-pills">
<li>{{#link-to "admin.backups.index"}}{{i18n 'admin.backups.menu.backups'}}{{/link-to}}</li>
<li>{{#link-to "admin.backups.logs"}}{{i18n 'admin.backups.menu.logs'}}{{/link-to}}</li>
{{admin-nav-item route='admin.backups.index' label='admin.backups.menu.backups'}}
{{admin-nav-item route='admin.backups.logs' label='admin.backups.menu.logs'}}
</ul>
</div>
<div class="pull-right">
{{#if canRollback}}
<button {{action "rollback"}} class="btn btn-rollback" title="{{i18n 'admin.backups.operations.rollback.title'}}" {{bind-attr disabled="rollbackDisabled"}}><i class="fa fa-ambulance fa-flip-horizontal"></i>{{i18n 'admin.backups.operations.rollback.text'}}</button>
{{d-button action="rollback"
class="btn-rollback"
label="admin.backups.operations.rollback.label"
title="admin.backups.operations.rollback.title"
icon="ambulance"
disabled=rollbackDisabled}}
{{/if}}
{{#if isOperationRunning}}
<button {{action "cancelOperation"}} class="btn btn-danger" title="{{i18n 'admin.backups.operations.cancel.title'}}"><i class="fa fa-times"></i>{{i18n 'admin.backups.operations.cancel.text'}}</button>
{{d-button action="cancelOperation"
class="btn-danger"
title="admin.backups.operations.cancel.title"
label="admin.backups.operations.cancel.label"
icon="times"}}
{{else}}
<button {{action "startBackup"}} class="btn btn-primary" title="{{i18n 'admin.backups.operations.backup.title'}}"><i class="fa fa-rocket"></i>{{i18n 'admin.backups.operations.backup.text'}}</button>
{{d-button action="startBackup"
class="btn-primary"
title="admin.backups.operations.backup.title"
label="admin.backups.operations.backup.label"
icon="rocket"}}
{{/if}}
</div>
</div>

View File

@ -4,8 +4,12 @@
<th width="10%">{{i18n 'admin.backups.columns.size'}}</th>
<th>
<div class="pull-right">
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadText}}
<button {{action "toggleReadOnlyMode"}} class="btn" {{bind-attr disabled="readOnlyModeDisabled" title="readOnlyModeTitle"}}><i class="fa fa-eye"></i>{{readOnlyModeText}}</button>
{{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"}}
{{else}}
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
{{/if}}
</div>
</th>
</tr>
@ -15,9 +19,14 @@
<td>{{human-size backup.size}}</td>
<td>
<div class="pull-right">
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n 'admin.backups.operations.download.title'}}"><i class="fa fa-download"></i>{{i18n 'admin.backups.operations.download.text'}}</a>
<button {{action "destroyBackup" backup}} class="btn btn-danger no-text" {{bind-attr disabled="destroyDisabled" title="destroyTitle"}}><i class="fa fa-trash-o"></i></button>
<button {{action "startRestore" backup}} class="btn" {{bind-attr disabled="restoreDisabled" title="restoreTitle"}}><i class="fa fa-play"></i>{{i18n 'admin.backups.operations.restore.text'}}</button>
<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}}
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger no-text" 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"}}
{{else}}
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger no-text" title="admin.backups.operations.destroy.title"}}
{{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
{{/if}}
</div>
</td>
</tr>

View File

@ -1,4 +1,4 @@
<div class='span13'>
<div class='current-badge span13'>
<p>{{i18n 'admin.badges.none_selected'}}</p>
<div>

View File

@ -0,0 +1,9 @@
{{#if routeParam}}
{{#link-to route routeParam}}{{i18n label}}{{/link-to}}
{{else}}
{{#if route}}
{{#link-to route}}{{i18n label}}{{/link-to}}
{{else}}
<a href="{{unbound href}}" data-auto-route="true">{{i18n label}}</a>
{{/if}}
{{/if}}

View File

@ -0,0 +1,7 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
{{yield}}
</ul>
</div>
</div>

View File

@ -0,0 +1,11 @@
<td class="title">
{{#if report.icon}}
{{fa-icon report.icon}}
{{/if}}
<a href="{{report.reportUrl}}">{{report.title}}</a>
</td>
<td class="value">{{report.todayCount}}</td>
<td {{bind-attr class=":value report.yesterdayTrend"}} title="{{report.yesterdayCountTitle}}">{{report.yesterdayCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
<td {{bind-attr class=":value report.sevenDayTrend"}} title="{{report.sevenDayCountTitle}}">{{report.lastSevenDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
<td {{bind-attr class=":value report.thirtyDayTrend"}} title="{{report.thirtyDayCountTitle}}">{{report.lastThirtyDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}</td>
<td class="value">{{report.total}}</td>

View File

@ -0,0 +1,6 @@
<td class="title"><a href="report.reportUrl}}">{{report.title}}</a></td>
<td class="value">{{report.todayCount}}</td>
<td class="value">{{report.yesterdayCount}}</td>
<td class="value">{{report.sevenDaysAgoCount}}</td>
<td class="value">{{report.thirtyDaysAgoCount}}</td>
<td class="value"></td>

View File

@ -0,0 +1,6 @@
<td class="title">{{report.title}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'newuser'}}{{value-at-tl report.data level="0"}}{{/link-to}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'basic'}}{{value-at-tl report.data level="1"}}{{/link-to}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'regular'}}{{value-at-tl report.data level="2"}}{{/link-to}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'leader'}}{{value-at-tl report.data level="3"}}{{/link-to}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'elder'}}{{value-at-tl report.data level="4"}}{{/link-to}}</td>

View File

@ -0,0 +1,4 @@
<div {{bind-attr class=":validation-error message::hidden"}}>
{{fa-icon "times"}}
{{message}}
</div>

View File

@ -0,0 +1,16 @@
<div class='setting-label'>
<h3>{{unbound settingName}}</h3>
</div>
<div class="setting-value">
{{partial partialName}}
</div>
{{#if dirty}}
<div class='setting-controls'>
{{d-button class="ok no-text" action="save" icon="check"}}
{{d-button class="cancel no-text" action="cancel" icon="times"}}
</div>
{{else}}
{{#if setting.overridden}}
{{d-button action="resetDefault" icon="undo" label="admin.site_settings.reset"}}
{{/if}}
{{/if}}

View File

@ -0,0 +1,18 @@
{{#if urls}}
<div class='urls'>
{{#each url in urls}}
<div class='url'>
{{d-button action="removeUrl"
actionParam=url
icon="times"
class="btn-small no-text"}}
<a href="{{unbound url}}" target="_blank">{{url}}</a>
</div>
{{/each}}
</div>
{{/if}}
<div class='input'>
{{text-field value=newUrl placeholderKey="admin.site_settings.add_url"}}
{{d-button action="addUrl" icon="plus" class="btn-primary btn-small no-text" disabled=urlInvalid}}
</div>

View File

@ -1,14 +1,10 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminCustomize.colors'}}{{i18n 'admin.customize.colors.title'}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomize.css_html'}}{{i18n 'admin.customize.css_html.title'}}{{/link-to}}</li>
<li>{{#link-to 'adminSiteText'}}{{i18n 'admin.site_text.title'}}{{/link-to}}</li>
<li>{{#link-to 'adminUserFields'}}{{i18n 'admin.user_fields.title'}}{{/link-to}}</li>
<li>{{#link-to 'adminEmojis'}}{{i18n 'admin.emoji.title'}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
{{admin-nav-item route='adminCustomize.css_html' label='admin.customize.css_html.title'}}
{{admin-nav-item route='adminSiteText' label='admin.site_text.title'}}
{{admin-nav-item route='adminUserFields' label='admin.user_fields.title'}}
{{admin-nav-item route='adminEmojis' label='admin.emoji.title'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -33,27 +33,29 @@
<li class='toggle-mobile'>
<a {{bind-attr class="view.mobile:active"}} {{action "toggleMobile" target="view"}}>{{fa-icon "mobile"}}</a>
</li>
<li class='toggle-maximize'><a {{action "toggleMaximize" target="view"}}>
<li class='toggle-maximize'>
<a {{action "toggleMaximize" target="view"}}>
{{#if view.maximized}}
{{fa-icon "compress"}}
{{else}}
{{fa-icon "expand"}}
{{/if}}
</a>
</li>
</ul>
</div>
<div class="admin-container">
{{#if view.stylesheetActive}}{{aceEditor content=selectedItem.stylesheet mode="scss"}}{{/if}}
{{#if view.headerActive}}{{aceEditor content=selectedItem.header mode="html"}}{{/if}}
{{#if view.topActive}}{{aceEditor content=selectedItem.top mode="html"}}{{/if}}
{{#if view.footerActive}}{{aceEditor content=selectedItem.footer mode="html"}}{{/if}}
{{#if view.headTagActive}}{{aceEditor content=selectedItem.head_tag mode="html"}}{{/if}}
{{#if view.bodyTagActive}}{{aceEditor content=selectedItem.body_tag mode="html"}}{{/if}}
{{#if view.mobileStylesheetActive}}{{aceEditor content=selectedItem.mobile_stylesheet mode="scss"}}{{/if}}
{{#if view.mobileHeaderActive}}{{aceEditor content=selectedItem.mobile_header mode="html"}}{{/if}}
{{#if view.mobileTopActive}}{{aceEditor content=selectedItem.mobile_top mode="html"}}{{/if}}
{{#if view.mobileFooterActive}}{{aceEditor content=selectedItem.mobile_footer mode="html"}}{{/if}}
{{#if view.stylesheetActive}}{{ace-editor content=selectedItem.stylesheet mode="scss"}}{{/if}}
{{#if view.headerActive}}{{ace-editor content=selectedItem.header mode="html"}}{{/if}}
{{#if view.topActive}}{{ace-editor content=selectedItem.top mode="html"}}{{/if}}
{{#if view.footerActive}}{{ace-editor content=selectedItem.footer mode="html"}}{{/if}}
{{#if view.headTagActive}}{{ace-editor content=selectedItem.head_tag mode="html"}}{{/if}}
{{#if view.bodyTagActive}}{{ace-editor content=selectedItem.body_tag mode="html"}}{{/if}}
{{#if view.mobileStylesheetActive}}{{ace-editor content=selectedItem.mobile_stylesheet mode="scss"}}{{/if}}
{{#if view.mobileHeaderActive}}{{ace-editor content=selectedItem.mobile_header mode="html"}}{{/if}}
{{#if view.mobileTopActive}}{{ace-editor content=selectedItem.mobile_top mode="html"}}{{/if}}
{{#if view.mobileFooterActive}}{{ace-editor content=selectedItem.mobile_footer mode="html"}}{{/if}}
</div>
<div class='admin-footer'>
<div class='status-actions'>

View File

@ -1,6 +1,6 @@
<div class="dashboard-left">
{{#if showVersionChecks}}
{{partial 'admin/templates/version_checks'}}
{{partial 'admin/templates/version-checks'}}
{{/if}}
<div class="dashboard-stats trust-levels">
@ -15,9 +15,11 @@
<th>4</th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{ render 'admin/templates/reports/trust_levels_report' users_by_trust_level tagName="tbody" }}
{{admin-report-trust-level-counts report=users_by_trust_level}}
{{/unless}}
</tbody>
</table>
</div>
@ -50,18 +52,45 @@
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{ render 'admin_report_counts' signups }}
{{ render 'admin_report_counts' topics }}
{{ render 'admin_report_counts' posts }}
{{ render 'admin_report_counts' likes }}
{{ render 'admin_report_counts' flags }}
{{ render 'admin_report_counts' bookmarks }}
{{ render 'admin_report_counts' emails }}
{{admin-report-per-day-counts report=visits}}
{{admin-report-counts report=signups}}
{{admin-report-counts report=topics}}
{{admin-report-counts report=posts}}
{{admin-report-counts report=likes}}
{{admin-report-counts report=flags}}
{{admin-report-counts report=bookmarks}}
{{admin-report-counts report=emails}}
{{/unless}}
</tbody>
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.page_views'}}">{{i18n 'admin.dashboard.page_views_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{admin-report-counts report=page_view_anon_reqs}}
{{admin-report-counts report=page_view_logged_in_reqs}}
{{admin-report-counts report=page_view_crawler_reqs}}
{{admin-report-counts report=page_view_total_reqs}}
{{/unless}}
</tbody>
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
@ -74,13 +103,15 @@
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{ render 'admin_report_counts' user_to_user_private_messages }}
{{ render 'admin_report_counts' system_private_messages }}
{{ render 'admin_report_counts' notify_moderators_private_messages }}
{{ render 'admin_report_counts' notify_user_private_messages }}
{{ render 'admin_report_counts' moderator_warning_private_messages }}
{{admin-report-counts report=user_to_user_private_messages}}
{{admin-report-counts report=system_private_messages}}
{{admin-report-counts report=notify_moderators_private_messages}}
{{admin-report-counts report=notify_user_private_messages}}
{{admin-report-counts report=moderator_warning_private_messages}}
{{/unless}}
</tbody>
</table>
</div>
@ -89,17 +120,56 @@
<thead>
<tr>
<th>&nbsp;</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.7_days_ago'}}</th>
<th>{{i18n 'admin.dashboard.reports.30_days_ago'}}</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{ render 'admin/templates/reports/per_day_counts_report' visits tagName="tbody"}}
<tr>
<td>{{i18n 'admin.dashboard.uploads'}}</td>
<td>{{disk_space.uploads_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.uploads_free}})</td>
<td><a href="/admin/backups">{{i18n 'admin.dashboard.backups'}}</a></td>
<td>{{disk_space.backups_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.backups_free}})</td>
</tr>
{{/unless}}
</tbody>
</table>
</div>
{{#unless loading}}
{{#if showTrafficReport}}
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.traffic'}}">{{i18n 'admin.dashboard.traffic_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{admin-report-counts report=http_2xx_reqs}}
{{admin-report-counts report=http_3xx_reqs}}
{{admin-report-counts report=http_4xx_reqs}}
{{admin-report-counts report=http_5xx_reqs}}
{{admin-report-counts report=http_background_reqs}}
{{admin-report-counts report=http_total_reqs}}
{{/unless}}
</tbody>
</table>
</div>
{{else}}
<div class="dashboard-stats">
<a href {{action 'showTrafficReport'}}>{{i18n 'admin.dashboard.show_traffic_report'}}</a>
</div>
{{/if}}
{{/unless}}
</div>
<div class="dashboard-right">

View File

@ -1,14 +1,10 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminEmail.index'}}{{i18n 'admin.email.settings'}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.all'}}{{i18n 'admin.email.all'}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.sent'}}{{i18n 'admin.email.sent'}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.skipped'}}{{i18n 'admin.email.skipped'}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.previewDigest'}}{{i18n 'admin.email.preview_digest'}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminEmail.index' label='admin.email.settings'}}
{{admin-nav-item route='adminEmail.all' label='admin.email.all'}}
{{admin-nav-item route='adminEmail.sent' label='admin.email.sent'}}
{{admin-nav-item route='adminEmail.skipped' label='admin.email.skipped'}}
{{admin-nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -18,10 +18,10 @@
</div>
</div>
{{#loading-spinner condition=loading}}
{{#conditional-loading-spinner condition=loading}}
{{#if showHtml}}
{{{html_content}}}
{{else}}
<pre>{{{text_content}}}</pre>
{{/if}}
{{/loading-spinner}}
{{/conditional-loading-spinner}}

View File

@ -147,7 +147,7 @@
</tbody>
</table>
{{loading-spinner condition=view.loading}}
{{conditional-loading-spinner condition=view.loading}}
{{else}}
<p>{{i18n 'admin.flags.no_results'}}</p>
{{/if}}

View File

@ -1,11 +1,7 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminFlags.list' 'active'}}{{i18n 'admin.flags.active'}}{{/link-to}}</li>
<li>{{#link-to 'adminFlags.list' 'old'}}{{i18n 'admin.flags.old'}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminFlags.list' routeParam='active' label='admin.flags.active'}}
{{admin-nav-item route='adminFlags.list' routeParam='old' label='admin.flags.old'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -38,6 +38,15 @@
</label>
</div>
{{#unless automatic}}
<div>
<label for="primary_group">
{{input type="checkbox" checked=primary_group}}
{{i18n 'admin.groups.primary_group'}}
</label>
</div>
{{/unless}}
<div>
<label for="alias">{{i18n 'groups.alias_levels.title'}}</label>
{{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
@ -52,6 +61,13 @@
{{i18n 'admin.groups.automatic_membership_retroactive'}}
</label>
</div>
<div>
<label for="title">
{{i18n 'admin.groups.default_title'}}
</label>
{{input value=title}}
</div>
{{/unless}}
<div class='buttons'>

View File

@ -1 +1 @@
{{avatar member imageSize="small"}} {{member.username}} {{#unless automatic}}<a class='remove' {{action "removeMember" member}}>{{fa-icon "times"}}</a>{{/unless}}
<a href='{{unbound member.adminPath}}'>{{avatar member imageSize="small"}}</a> {{member.username}} {{#unless automatic}}<a class='remove' {{action "removeMember" member}}>{{fa-icon "times"}}</a>{{/unless}}

View File

@ -1,11 +1,8 @@
<div class="admin-controls">
<div class="span15">
<ul class="nav nav-pills">
<li>{{#link-to "adminGroupsType" "custom"}}{{i18n 'admin.groups.custom'}}{{/link-to}}</li>
<li>{{#link-to "adminGroupsType" "automatic"}}{{i18n 'admin.groups.automatic'}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}}
{{admin-nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}
</div>

View File

@ -10,7 +10,9 @@
</ul>
<div class='controls'>
{{d-button action="newGroup" icon="plus" label="admin.groups.new"}}
{{#if controller.isAuto}}
{{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}}
{{/if}}
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More