Compare commits

..

1 Commits

Author SHA1 Message Date
Sam Saffron
8c1c9b2d30
FEATURE: add db:resize:notification_id task for growing table
Under exceptional cases people may need to resize the notification table.
This only happens on forums with a total of more than 2.5 billion notifications.

This rake task can be used to convert all the notification columns to
bigint to make more room.
2023-03-02 16:17:44 +11:00
1125 changed files with 19485 additions and 25182 deletions

View File

@ -159,22 +159,6 @@ jobs:
path: tmp/turbo_rspec_runtime.log path: tmp/turbo_rspec_runtime.log
key: rspec-runtime-backend-core key: rspec-runtime-backend-core
- name: Run Zeitwerk check
if: matrix.build_type == 'backend'
env:
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
run: |
if ! bin/rails zeitwerk:check --trace; then
echo
echo "---------------------------------------------"
echo
echo "::error::'bin/rails zeitwerk:check' failed - the app will fail to boot with 'eager_load=true' (e.g. in production)."
echo "To reproduce locally, run 'bin/rails zeitwerk:check'."
echo "Alternatively, you can run your local server/tests with the 'DISCOURSE_ZEITWERK_EAGER_LOAD=1' environment variable."
echo
exit 1
fi
- name: Core RSpec - name: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core' if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --verbose run: bin/turbo_rspec --verbose
@ -198,11 +182,11 @@ jobs:
- name: Core System Tests - name: Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core' if: matrix.build_type == 'system' && matrix.target == 'core'
run: bin/rspec spec/system run: PARALLEL_TEST_PROCESSORS=1 bin/turbo_rspec --verbose spec/system
- name: Plugin System Tests - name: Plugin System Tests
if: matrix.build_type == 'system' && matrix.target == 'plugins' if: matrix.build_type == 'system' && matrix.target == 'plugins'
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system run: LOAD_PLUGINS=1 PARALLEL_TEST_PROCESSORS=1 bin/turbo_rspec --verbose plugins/*/spec/system
- name: Upload failed system test screenshots - name: Upload failed system test screenshots
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@ -15,7 +15,7 @@ module.exports = {
"directory-item-value", "directory-item-value",
"directory-table-header-title", "directory-table-header-title",
"loading-spinner", "loading-spinner",
"directory-item-label", "mobile-directory-item-label",
], ],
}, },
"no-implicit-this": { "no-implicit-this": {

View File

@ -18,7 +18,7 @@ else
# this allows us to include the bits of rails we use without pieces we do not. # this allows us to include the bits of rails we use without pieces we do not.
# #
# To issue a rails update bump the version number here # To issue a rails update bump the version number here
rails_version = "7.0.4.3" rails_version = "7.0.4.1"
gem "actionmailer", rails_version gem "actionmailer", rails_version
gem "actionpack", rails_version gem "actionpack", rails_version
gem "actionview", rails_version gem "actionview", rails_version

View File

@ -17,25 +17,25 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionmailer (7.0.4.3) actionmailer (7.0.4.1)
actionpack (= 7.0.4.3) actionpack (= 7.0.4.1)
actionview (= 7.0.4.3) actionview (= 7.0.4.1)
activejob (= 7.0.4.3) activejob (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (7.0.4.3) actionpack (7.0.4.1)
actionview (= 7.0.4.3) actionview (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0) rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (7.0.4.3) actionview (7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -44,15 +44,15 @@ GEM
actionview (>= 6.0.a) actionview (>= 6.0.a)
active_model_serializers (0.8.4) active_model_serializers (0.8.4)
activemodel (>= 3.0) activemodel (>= 3.0)
activejob (7.0.4.3) activejob (7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.4.3) activemodel (7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
activerecord (7.0.4.3) activerecord (7.0.4.1)
activemodel (= 7.0.4.3) activemodel (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
activesupport (7.0.4.3) activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -157,7 +157,7 @@ GEM
faraday-net_http (>= 2.0, < 3.1) faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2) faraday-net_http (3.0.2)
faraday-retry (2.1.0) faraday-retry (2.0.0)
faraday (~> 2.0) faraday (~> 2.0)
fast_blank (1.0.1) fast_blank (1.0.1)
fast_xs (0.8.0) fast_xs (0.8.0)
@ -167,11 +167,10 @@ GEM
gc_tracer (1.5.1) gc_tracer (1.5.1)
globalid (1.1.0) globalid (1.1.0)
activesupport (>= 5.0) activesupport (>= 5.0)
google-protobuf (3.22.2) google-protobuf (3.22.0)
google-protobuf (3.22.2-aarch64-linux) google-protobuf (3.22.0-arm64-darwin)
google-protobuf (3.22.2-arm64-darwin) google-protobuf (3.22.0-x86_64-darwin)
google-protobuf (3.22.2-x86_64-darwin) google-protobuf (3.22.0-x86_64-linux)
google-protobuf (3.22.2-x86_64-linux)
guess_html_encoding (0.0.11) guess_html_encoding (0.0.11)
hana (1.3.7) hana (1.3.7)
hashdiff (1.0.1) hashdiff (1.0.1)
@ -219,7 +218,7 @@ GEM
logstash-event (1.2.02) logstash-event (1.2.02)
logstash-logger (0.26.1) logstash-logger (0.26.1)
logstash-event (~> 1.2) logstash-event (~> 1.2)
logster (2.12.2) logster (2.11.4)
loofah (2.19.1) loofah (2.19.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
@ -240,10 +239,10 @@ GEM
mini_sql (1.4.0) mini_sql (1.4.0)
mini_suffix (0.3.3) mini_suffix (0.3.3)
ffi (~> 1.9) ffi (~> 1.9)
minitest (5.18.0) minitest (5.17.0)
mocha (2.0.2) mocha (2.0.2)
ruby2_keywords (>= 0.0.5) ruby2_keywords (>= 0.0.5)
msgpack (1.6.1) msgpack (1.6.0)
multi_json (1.15.0) multi_json (1.15.0)
multi_xml (0.6.0) multi_xml (0.6.0)
mustache (1.1.1) mustache (1.1.1)
@ -312,10 +311,10 @@ GEM
parallel (1.22.1) parallel (1.22.1)
parallel_tests (4.2.0) parallel_tests (4.2.0)
parallel parallel
parser (3.2.1.1) parser (3.2.1.0)
ast (~> 2.4.1) ast (~> 2.4.1)
pg (1.4.6) pg (1.4.6)
prettier_print (1.2.1) prettier_print (1.2.0)
progress (3.6.0) progress (3.6.0)
pry (0.14.2) pry (0.14.2)
coderay (~> 1.1) coderay (~> 1.1)
@ -329,12 +328,12 @@ GEM
puma (6.1.1) puma (6.1.1)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.6.2) racc (1.6.2)
rack (2.2.6.4) rack (2.2.6.2)
rack-mini-profiler (3.0.0) rack-mini-profiler (3.0.0)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-protection (3.0.5) rack-protection (3.0.5)
rack rack
rack-test (2.1.0) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -348,9 +347,9 @@ GEM
rails_multisite (4.0.1) rails_multisite (4.0.1)
activerecord (> 5.0, < 7.1) activerecord (> 5.0, < 7.1)
railties (> 5.0, < 7.1) railties (> 5.0, < 7.1)
railties (7.0.4.3) railties (7.0.4.1)
actionpack (= 7.0.4.3) actionpack (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
@ -391,7 +390,7 @@ GEM
rspec-html-matchers (0.10.0) rspec-html-matchers (0.10.0)
nokogiri (~> 1) nokogiri (~> 1)
rspec (>= 3.0.0.a) rspec (>= 3.0.0.a)
rspec-mocks (3.12.4) rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-rails (6.0.1) rspec-rails (6.0.1)
@ -412,7 +411,7 @@ GEM
rspec-core (>= 2.14) rspec-core (>= 2.14)
rtlcss (0.2.0) rtlcss (0.2.0)
mini_racer (~> 0.6.3) mini_racer (~> 0.6.3)
rubocop (1.48.1) rubocop (1.47.0)
json (~> 2.3) json (~> 2.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.2.0.0) parser (>= 3.2.0.0)
@ -426,14 +425,14 @@ GEM
parser (>= 3.2.1.0) parser (>= 3.2.1.0)
rubocop-capybara (2.17.1) rubocop-capybara (2.17.1)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-discourse (3.2.0) rubocop-discourse (3.1.0)
rubocop (>= 1.1.0) rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0) rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.19.0) rubocop-rspec (2.18.1)
rubocop (~> 1.33) rubocop (~> 1.33)
rubocop-capybara (~> 2.17) rubocop-capybara (~> 2.17)
ruby-prof (1.6.1) ruby-prof (1.6.1)
ruby-progressbar (1.13.0) ruby-progressbar (1.12.0)
ruby-readability (0.7.0) ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4) guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0) nokogiri (>= 1.6.0)
@ -442,16 +441,16 @@ GEM
sanitize (6.0.1) sanitize (6.0.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
sass-embedded (1.59.2) sass-embedded (1.58.3)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
rake (>= 10.0.0) rake (>= 10.0.0)
sass-embedded (1.59.2-aarch64-linux-gnu) sass-embedded (1.58.3-aarch64-linux-gnu)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
sass-embedded (1.59.2-arm64-darwin) sass-embedded (1.58.3-arm64-darwin)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-darwin) sass-embedded (1.58.3-x86_64-darwin)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-linux-gnu) sass-embedded (1.58.3-x86_64-linux-gnu)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
selenium-webdriver (4.8.1) selenium-webdriver (4.8.1)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
@ -478,7 +477,7 @@ GEM
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sshkey (2.0.0) sshkey (2.0.0)
stackprof (0.2.23) stackprof (0.2.23)
syntax_tree (6.0.2) syntax_tree (6.0.1)
prettier_print (>= 1.2.0) prettier_print (>= 1.2.0)
syntax_tree-disable_ternary (1.0.0) syntax_tree-disable_ternary (1.0.0)
test-prof (1.2.0) test-prof (1.2.0)
@ -534,14 +533,14 @@ PLATFORMS
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
actionmailer (= 7.0.4.3) actionmailer (= 7.0.4.1)
actionpack (= 7.0.4.3) actionpack (= 7.0.4.1)
actionview (= 7.0.4.3) actionview (= 7.0.4.1)
actionview_precompiler actionview_precompiler
active_model_serializers (~> 0.8.3) active_model_serializers (~> 0.8.3)
activemodel (= 7.0.4.3) activemodel (= 7.0.4.1)
activerecord (= 7.0.4.3) activerecord (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
addressable addressable
annotate annotate
aws-sdk-s3 aws-sdk-s3
@ -627,7 +626,7 @@ DEPENDENCIES
rack-protection rack-protection
rails_failover rails_failover
rails_multisite rails_multisite
railties (= 7.0.4.3) railties (= 7.0.4.1)
rake rake
rb-fsevent rb-fsevent
rbtrace rbtrace

View File

@ -1,13 +1,13 @@
import RestAdapter from "discourse/adapters/rest"; import RESTAdapter from "discourse/adapters/rest";
export default class ApiKey extends RestAdapter { export default RESTAdapter.extend({
jsonMode = true; jsonMode: true,
basePath() { basePath() {
return "/admin/api/"; return "/admin/api/";
} },
apiNameFor() { apiNameFor() {
return "key"; return "key";
} },
} });

View File

@ -1,11 +1,11 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default function buildPluginAdapter(pluginName) { export default function buildPluginAdapter(pluginName) {
return class extends RestAdapter { return RestAdapter.extend({
pathFor(store, type, findArgs) { pathFor(store, type, findArgs) {
return ( return (
"/admin/plugins/" + pluginName + super.pathFor(store, type, findArgs) "/admin/plugins/" + pluginName + this._super(store, type, findArgs)
); );
} },
}; });
} }

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default class CustomizationBase extends RestAdapter { export default RestAdapter.extend({
basePath() { basePath() {
return "/admin/customize/"; return "/admin/customize/";
} },
} });

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default class EmailStyle extends RestAdapter { export default RestAdapter.extend({
pathFor() { pathFor() {
return "/admin/customize/email_style"; return "/admin/customize/email_style";
} },
} });

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default class Embedding extends RestAdapter { export default RestAdapter.extend({
pathFor() { pathFor() {
return "/admin/customize/embedding"; return "/admin/customize/embedding";
} },
} });

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default class StaffActionLog extends RestAdapter { export default RestAdapter.extend({
basePath() { basePath() {
return "/admin/logs/"; return "/admin/logs/";
} },
} });

View File

@ -1,5 +1,5 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default class TagGroup extends RestAdapter { export default RestAdapter.extend({
jsonMode = true; jsonMode: true,
} });

View File

@ -1,10 +1,9 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default class Theme extends RestAdapter { export default RestAdapter.extend({
jsonMode = true;
basePath() { basePath() {
return "/admin/"; return "/admin/";
} },
afterFindAll(results) { afterFindAll(results) {
let map = {}; let map = {};
@ -21,5 +20,7 @@ export default class Theme extends RestAdapter {
theme.set("parentThemes", mappedParents); theme.set("parentThemes", mappedParents);
}); });
return results; return results;
} },
}
jsonMode: true,
});

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest"; import RESTAdapter from "discourse/adapters/rest";
export default class WebHookEvent extends RestAdapter { export default RESTAdapter.extend({
basePath() { basePath() {
return "/admin/api/"; return "/admin/api/";
} },
} });

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest"; import RESTAdapter from "discourse/adapters/rest";
export default class WebHook extends RestAdapter { export default RESTAdapter.extend({
basePath() { basePath() {
return "/admin/api/"; return "/admin/api/";
} },
} });

View File

@ -56,7 +56,6 @@
{{#if this.hasInactiveThemes}} {{#if this.hasInactiveThemes}}
{{#each this.inactiveThemes as |theme|}} {{#each this.inactiveThemes as |theme|}}
<ThemesListItem <ThemesListItem
@classNames="inactive-theme"
@theme={{theme}} @theme={{theme}}
@navigateToTheme={{action "navigateToTheme" theme}} @navigateToTheme={{action "navigateToTheme" theme}}
/> />

View File

@ -2,18 +2,18 @@ import Controller from "@ember/controller";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminApiKeysIndexController extends Controller { export default Controller.extend({
loading = false; loading: false,
@action @action
revokeKey(key) { revokeKey(key) {
key.revoke().catch(popupAjaxError); key.revoke().catch(popupAjaxError);
} },
@action @action
undoRevokeKey(key) { undoRevokeKey(key) {
key.undoRevoke().catch(popupAjaxError); key.undoRevoke().catch(popupAjaxError);
} },
@action @action
loadMore() { loadMore() {
@ -35,5 +35,5 @@ export default class AdminApiKeysIndexController extends Controller {
.finally(() => { .finally(() => {
this.set("loading", false); this.set("loading", false);
}); });
} },
} });

View File

@ -1,32 +1,37 @@
import { equal } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { isBlank } from "@ember/utils"; import { isBlank } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { action, get } from "@ember/object"; import { action, get } from "@ember/object";
import { equal } from "@ember/object/computed";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
export default class AdminApiKeysNewController extends Controller { export default Controller.extend({
userModes = [ userModes: null,
{ id: "all", name: I18n.t("admin.api.all_users") }, scopeModes: null,
{ id: "single", name: I18n.t("admin.api.single_user") }, globalScopes: null,
]; scopes: null,
scopeModes = [
{ id: "granular", name: I18n.t("admin.api.scopes.granular") },
{ id: "read_only", name: I18n.t("admin.api.scopes.read_only") },
{ id: "global", name: I18n.t("admin.api.scopes.global") },
];
globalScopes = null;
scopes = null;
@equal("userMode", "single") showUserSelector;
init() { init() {
super.init(...arguments); this._super(...arguments);
this.set("userModes", [
{ id: "all", name: I18n.t("admin.api.all_users") },
{ id: "single", name: I18n.t("admin.api.single_user") },
]);
this.set("scopeModes", [
{ id: "granular", name: I18n.t("admin.api.scopes.granular") },
{ id: "read_only", name: I18n.t("admin.api.scopes.read_only") },
{ id: "global", name: I18n.t("admin.api.scopes.global") },
]);
this._loadScopes(); this._loadScopes();
} },
showUserSelector: equal("userMode", "single"),
@discourseComputed("model.{description,username}", "showUserSelector") @discourseComputed("model.{description,username}", "showUserSelector")
saveDisabled(model, showUserSelector) { saveDisabled(model, showUserSelector) {
@ -37,12 +42,12 @@ export default class AdminApiKeysNewController extends Controller {
return true; return true;
} }
return false; return false;
} },
@action @action
updateUsername(selected) { updateUsername(selected) {
this.set("model.username", get(selected, "firstObject")); this.set("model.username", get(selected, "firstObject"));
} },
@action @action
changeUserMode(userMode) { changeUserMode(userMode) {
@ -50,12 +55,12 @@ export default class AdminApiKeysNewController extends Controller {
this.model.set("username", null); this.model.set("username", null);
} }
this.set("userMode", userMode); this.set("userMode", userMode);
} },
@action @action
changeScopeMode(scopeMode) { changeScopeMode(scopeMode) {
this.set("scopeMode", scopeMode); this.set("scopeMode", scopeMode);
} },
@action @action
save() { save() {
@ -72,12 +77,12 @@ export default class AdminApiKeysNewController extends Controller {
} }
return this.model.save().catch(popupAjaxError); return this.model.save().catch(popupAjaxError);
} },
@action @action
continue() { continue() {
this.transitionToRoute("adminApiKeys.show", this.model.id); this.transitionToRoute("adminApiKeys.show", this.model.id);
} },
@action @action
showURLs(urls) { showURLs(urls) {
@ -85,7 +90,7 @@ export default class AdminApiKeysNewController extends Controller {
admin: true, admin: true,
model: { urls }, model: { urls },
}); });
} },
_loadScopes() { _loadScopes() {
return ajax("/admin/api/keys/scopes.json") return ajax("/admin/api/keys/scopes.json")
@ -97,5 +102,5 @@ export default class AdminApiKeysNewController extends Controller {
this.set("scopes", data.scopes); this.set("scopes", data.scopes);
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
} });

View File

@ -1,74 +1,66 @@
import { action } from "@ember/object";
import { empty } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import { empty } from "@ember/object/computed";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
export default class AdminApiKeysShowController extends Controller.extend( export default Controller.extend(bufferedProperty("model"), {
bufferedProperty("model") isNew: empty("model.id"),
) {
@empty("model.id") isNew;
@action actions: {
saveDescription() { saveDescription() {
const buffered = this.buffered; const buffered = this.buffered;
const attrs = buffered.getProperties("description"); const attrs = buffered.getProperties("description");
this.model this.model
.save(attrs) .save(attrs)
.then(() => { .then(() => {
this.set("editingDescription", false); this.set("editingDescription", false);
this.rollbackBuffer();
})
.catch(popupAjaxError);
},
cancel() {
const id = this.get("userField.id");
if (isEmpty(id)) {
this.destroyAction(this.userField);
} else {
this.rollbackBuffer(); this.rollbackBuffer();
}) this.set("editing", false);
.catch(popupAjaxError); }
} },
@action editDescription() {
cancel() { this.toggleProperty("editingDescription");
const id = this.get("userField.id"); if (!this.editingDescription) {
if (isEmpty(id)) { this.rollbackBuffer();
this.destroyAction(this.userField); }
} else { },
this.rollbackBuffer();
this.set("editing", false);
}
}
@action revokeKey(key) {
editDescription() { key.revoke().catch(popupAjaxError);
this.toggleProperty("editingDescription"); },
if (!this.editingDescription) {
this.rollbackBuffer();
}
}
@action deleteKey(key) {
revokeKey(key) { key
key.revoke().catch(popupAjaxError); .destroyRecord()
} .then(() => this.transitionToRoute("adminApiKeys.index"))
.catch(popupAjaxError);
},
@action undoRevokeKey(key) {
deleteKey(key) { key.undoRevoke().catch(popupAjaxError);
key },
.destroyRecord()
.then(() => this.transitionToRoute("adminApiKeys.index"))
.catch(popupAjaxError);
}
@action showURLs(urls) {
undoRevokeKey(key) { return showModal("admin-api-key-urls", {
key.undoRevoke().catch(popupAjaxError); admin: true,
} model: {
urls,
@action },
showURLs(urls) { });
return showModal("admin-api-key-urls", { },
admin: true, },
model: { });
urls,
},
});
}
}

View File

@ -1,3 +1,3 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
export default class AdminApiKeysController extends Controller {} export default Controller.extend();

View File

@ -1,21 +1,19 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { alias, equal } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { alias, equal } from "@ember/object/computed";
import { i18n, setting } from "discourse/lib/computed"; import { i18n, setting } from "discourse/lib/computed";
import I18n from "I18n"; import I18n from "I18n";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminBackupsIndexController extends Controller { export default Controller.extend({
@service dialog; adminBackups: controller(),
@controller adminBackups; dialog: service(),
status: alias("adminBackups.model"),
@alias("adminBackups.model") status; uploadLabel: i18n("admin.backups.upload.label"),
@i18n("admin.backups.upload.label") uploadLabel; backupLocation: setting("backup_location"),
@setting("backup_location") backupLocation; localBackupStorage: equal("backupLocation", "local"),
@equal("backupLocation", "local") localBackupStorage;
@discourseComputed("status.allowRestore", "status.isOperationRunning") @discourseComputed("status.allowRestore", "status.isOperationRunning")
restoreTitle(allowRestore, isOperationRunning) { restoreTitle(allowRestore, isOperationRunning) {
@ -26,35 +24,35 @@ export default class AdminBackupsIndexController extends Controller {
} else { } else {
return "admin.backups.operations.restore.title"; return "admin.backups.operations.restore.title";
} }
} },
@action actions: {
toggleReadOnlyMode() { toggleReadOnlyMode() {
if (!this.site.get("isReadOnly")) { if (!this.site.get("isReadOnly")) {
this.dialog.yesNoConfirm({ this.dialog.yesNoConfirm({
message: I18n.t("admin.backups.read_only.enable.confirm"), message: I18n.t("admin.backups.read_only.enable.confirm"),
didConfirm: () => { didConfirm: () => {
this.set("currentUser.hideReadOnlyAlert", true); this.set("currentUser.hideReadOnlyAlert", true);
this._toggleReadOnlyMode(true); this._toggleReadOnlyMode(true);
}, },
}); });
} else { } else {
this._toggleReadOnlyMode(false); this._toggleReadOnlyMode(false);
} }
} },
@action download(backup) {
download(backup) { const link = backup.get("filename");
const link = backup.get("filename"); ajax(`/admin/backups/${link}`, { type: "PUT" }).then(() =>
ajax(`/admin/backups/${link}`, { type: "PUT" }).then(() => this.dialog.alert(I18n.t("admin.backups.operations.download.alert"))
this.dialog.alert(I18n.t("admin.backups.operations.download.alert")) );
); },
} },
_toggleReadOnlyMode(enable) { _toggleReadOnlyMode(enable) {
ajax("/admin/backups/readonly", { ajax("/admin/backups/readonly", {
type: "PUT", type: "PUT",
data: { enable }, data: { enable },
}).then(() => this.site.set("isReadOnly", enable)); }).then(() => this.site.set("isReadOnly", enable));
} },
} });

View File

@ -1,10 +1,13 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { alias } from "@ember/object/computed";
export default class AdminBackupsLogsController extends Controller { export default Controller.extend({
@controller adminBackups; adminBackups: controller(),
status: alias("adminBackups.model"),
@alias("adminBackups.model") status; init() {
this._super(...arguments);
logs = []; this.logs = [];
} },
});

View File

@ -1,8 +1,11 @@
import { and, not } from "@ember/object/computed"; import { and, not } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
export default class AdminBackupsController extends Controller { export default Controller.extend({
@not("model.isOperationRunning") noOperationIsRunning; noOperationIsRunning: not("model.isOperationRunning"),
@not("rollbackEnabled") rollbackDisabled; rollbackEnabled: and(
@and("model.canRollback", "model.restoreEnabled", "noOperationIsRunning") "model.canRollback",
rollbackEnabled; "model.restoreEnabled",
} "noOperationIsRunning"
),
rollbackDisabled: not("rollbackEnabled"),
});

View File

@ -1,19 +1,19 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import EmberObject, { action } from "@ember/object"; import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
export default class AdminCustomizeColorsController extends Controller { export default Controller.extend({
@discourseComputed("model.@each.id") @discourseComputed("model.@each.id")
baseColorScheme() { baseColorScheme() {
return this.model.findBy("is_base", true); return this.model.findBy("is_base", true);
} },
@discourseComputed("model.@each.id") @discourseComputed("model.@each.id")
baseColorSchemes() { baseColorSchemes() {
return this.model.filterBy("is_base", true); return this.model.filterBy("is_base", true);
} },
@discourseComputed("baseColorScheme") @discourseComputed("baseColorScheme")
baseColors(baseColorScheme) { baseColors(baseColorScheme) {
@ -22,28 +22,28 @@ export default class AdminCustomizeColorsController extends Controller {
baseColorsHash.set(color.get("name"), color); baseColorsHash.set(color.get("name"), color);
}); });
return baseColorsHash; return baseColorsHash;
} },
@action actions: {
newColorSchemeWithBase(baseKey) { newColorSchemeWithBase(baseKey) {
const base = this.baseColorSchemes.findBy("base_scheme_id", baseKey); const base = this.baseColorSchemes.findBy("base_scheme_id", baseKey);
const newColorScheme = base.copy(); const newColorScheme = base.copy();
newColorScheme.setProperties({ newColorScheme.setProperties({
name: I18n.t("admin.customize.colors.new_name"), name: I18n.t("admin.customize.colors.new_name"),
base_scheme_id: base.get("base_scheme_id"), base_scheme_id: base.get("base_scheme_id"),
}); });
newColorScheme.save().then(() => { newColorScheme.save().then(() => {
this.model.pushObject(newColorScheme); this.model.pushObject(newColorScheme);
newColorScheme.set("savingStatus", null); newColorScheme.set("savingStatus", null);
this.replaceRoute("adminCustomize.colors.show", newColorScheme); this.replaceRoute("adminCustomize.colors.show", newColorScheme);
}); });
} },
@action newColorScheme() {
newColorScheme() { showModal("admin-color-scheme-select-base", {
showModal("admin-color-scheme-select-base", { model: this.baseColorSchemes,
model: this.baseColorSchemes, admin: true,
admin: true, });
}); },
} },
} });

View File

@ -1,38 +1,38 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminCustomizeEmailStyleEditController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
@discourseComputed("model.isSaving") @discourseComputed("model.isSaving")
saveButtonText(isSaving) { saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save"); return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
} },
@discourseComputed("model.changed", "model.isSaving") @discourseComputed("model.changed", "model.isSaving")
saveDisabled(changed, isSaving) { saveDisabled(changed, isSaving) {
return !changed || isSaving; return !changed || isSaving;
} },
@action actions: {
save() { save() {
if (!this.model.saving) { if (!this.model.saving) {
this.set("saving", true); this.set("saving", true);
this.model this.model
.update(this.model.getProperties("html", "css")) .update(this.model.getProperties("html", "css"))
.catch((e) => { .catch((e) => {
const msg = const msg =
e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors
? I18n.t("admin.customize.email_style.save_error_with_reason", { ? I18n.t("admin.customize.email_style.save_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "), error: e.jqXHR.responseJSON.errors.join(". "),
}) })
: I18n.t("generic_error"); : I18n.t("generic_error");
this.dialog.alert(msg); this.dialog.alert(msg);
}) })
.finally(() => this.set("model.changed", false)); .finally(() => this.set("model.changed", false));
} }
} },
} },
});

View File

@ -1,26 +1,23 @@
import { inject as service } from "@ember/service";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminCustomizeEmailTemplatesEditController extends Controller.extend( export default Controller.extend(bufferedProperty("emailTemplate"), {
bufferedProperty("emailTemplate") adminCustomizeEmailTemplates: controller(),
) { dialog: service(),
@service dialog; emailTemplate: null,
@controller adminCustomizeEmailTemplates; saved: false,
emailTemplate = null;
saved = false;
@discourseComputed("buffered.body", "buffered.subject") @discourseComputed("buffered.body", "buffered.subject")
saveDisabled(body, subject) { saveDisabled(body, subject) {
return ( return (
this.emailTemplate.body === body && this.emailTemplate.subject === subject this.emailTemplate.body === body && this.emailTemplate.subject === subject
); );
} },
@discourseComputed("buffered") @discourseComputed("buffered")
hasMultipleSubjects(buffered) { hasMultipleSubjects(buffered) {
@ -29,7 +26,7 @@ export default class AdminCustomizeEmailTemplatesEditController extends Controll
} else { } else {
return buffered.getProperties("id")["id"]; return buffered.getProperties("id")["id"];
} }
} },
@action @action
saveChanges() { saveChanges() {
@ -41,7 +38,7 @@ export default class AdminCustomizeEmailTemplatesEditController extends Controll
this.set("saved", true); this.set("saved", true);
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
@action @action
revertChanges() { revertChanges() {
@ -60,5 +57,5 @@ export default class AdminCustomizeEmailTemplatesEditController extends Controll
.catch(popupAjaxError); .catch(popupAjaxError);
}, },
}); });
} },
} });

View File

@ -1,13 +1,18 @@
import { sort } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { sort } from "@ember/object/computed";
export default class AdminCustomizeEmailTemplatesController extends Controller { export default Controller.extend({
titleSorting = ["title"]; sortedTemplates: sort("emailTemplates", "titleSorting"),
@sort("emailTemplates", "titleSorting") sortedTemplates;
init() {
this._super(...arguments);
this.set("titleSorting", ["title"]);
},
@action @action
onSelectTemplate(template) { onSelectTemplate(template) {
this.transitionToRoute("adminCustomizeEmailTemplates.edit", template); this.transitionToRoute("adminCustomizeEmailTemplates.edit", template);
} },
} });

View File

@ -1,52 +1,47 @@
import { action } from "@ember/object";
import { not } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import { not } from "@ember/object/computed";
import { propertyEqual } from "discourse/lib/computed"; import { propertyEqual } from "discourse/lib/computed";
export default class AdminCustomizeRobotsTxtController extends Controller.extend( export default Controller.extend(bufferedProperty("model"), {
bufferedProperty("model") saved: false,
) { isSaving: false,
saved = false; saveDisabled: propertyEqual("model.robots_txt", "buffered.robots_txt"),
isSaving = false; resetDisabled: not("model.overridden"),
@propertyEqual("model.robots_txt", "buffered.robots_txt") saveDisabled; actions: {
save() {
this.setProperties({
isSaving: true,
saved: false,
});
@not("model.overridden") resetDisabled; ajax("robots.json", {
type: "PUT",
@action data: { robots_txt: this.buffered.get("robots_txt") },
save() {
this.setProperties({
isSaving: true,
saved: false,
});
ajax("robots.json", {
type: "PUT",
data: { robots_txt: this.buffered.get("robots_txt") },
})
.then((data) => {
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", data.overridden);
}) })
.finally(() => this.set("isSaving", false)); .then((data) => {
} this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", data.overridden);
})
.finally(() => this.set("isSaving", false));
},
@action reset() {
reset() { this.setProperties({
this.setProperties({ isSaving: true,
isSaving: true, saved: false,
saved: false, });
}); ajax("robots.json", { type: "DELETE" })
ajax("robots.json", { type: "DELETE" }) .then((data) => {
.then((data) => { this.buffered.set("robots_txt", data.robots_txt);
this.buffered.set("robots_txt", data.robots_txt); this.commitBuffer();
this.commitBuffer(); this.set("saved", true);
this.set("saved", true); this.set("model.overridden", false);
this.set("model.overridden", false); })
}) .finally(() => this.set("isSaving", false));
.finally(() => this.set("isSaving", false)); },
} },
} });

View File

@ -1,24 +1,21 @@
import { action } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { url } from "discourse/lib/computed"; import { url } from "discourse/lib/computed";
export default class AdminCustomizeThemesEditController extends Controller { export default Controller.extend({
section = null; section: null,
currentTarget = 0; currentTarget: 0,
maximized = false; maximized: false,
previewUrl: url("model.id", "/admin/themes/%@/preview"),
@url("model.id", "/admin/themes/%@/preview") previewUrl; showAdvanced: false,
editRouteName: "adminCustomizeThemes.edit",
showAdvanced = false; showRouteName: "adminCustomizeThemes.show",
editRouteName = "adminCustomizeThemes.edit";
showRouteName = "adminCustomizeThemes.show";
setTargetName(name) { setTargetName(name) {
const target = this.get("model.targets").find((t) => t.name === name); const target = this.get("model.targets").find((t) => t.name === name);
this.set("currentTarget", target && target.id); this.set("currentTarget", target && target.id);
} },
@discourseComputed("currentTarget") @discourseComputed("currentTarget")
currentTargetName(id) { currentTargetName(id) {
@ -26,52 +23,50 @@ export default class AdminCustomizeThemesEditController extends Controller {
(t) => t.id === parseInt(id, 10) (t) => t.id === parseInt(id, 10)
); );
return target && target.name; return target && target.name;
} },
@discourseComputed("model.isSaving") @discourseComputed("model.isSaving")
saveButtonText(isSaving) { saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save"); return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
} },
@discourseComputed("model.changed", "model.isSaving") @discourseComputed("model.changed", "model.isSaving")
saveDisabled(changed, isSaving) { saveDisabled(changed, isSaving) {
return !changed || isSaving; return !changed || isSaving;
} },
@action actions: {
save() { save() {
this.set("saving", true); this.set("saving", true);
this.model.saveChanges("theme_fields").finally(() => { this.model.saveChanges("theme_fields").finally(() => {
this.set("saving", false); this.set("saving", false);
}); });
} },
@action fieldAdded(target, name) {
fieldAdded(target, name) { this.replaceRoute(this.editRouteName, this.get("model.id"), target, name);
this.replaceRoute(this.editRouteName, this.get("model.id"), target, name); },
}
@action onlyOverriddenChanged(onlyShowOverridden) {
onlyOverriddenChanged(onlyShowOverridden) { if (onlyShowOverridden) {
if (onlyShowOverridden) { if (!this.model.hasEdited(this.currentTargetName, this.fieldName)) {
if (!this.model.hasEdited(this.currentTargetName, this.fieldName)) { let firstTarget = this.get("model.targets").find((t) => t.edited);
let firstTarget = this.get("model.targets").find((t) => t.edited); let firstField = this.get(`model.fields.${firstTarget.name}`).find(
let firstField = this.get(`model.fields.${firstTarget.name}`).find( (f) => f.edited
(f) => f.edited );
);
this.replaceRoute( this.replaceRoute(
this.editRouteName, this.editRouteName,
this.get("model.id"), this.get("model.id"),
firstTarget.name, firstTarget.name,
firstField.name firstField.name
); );
}
} }
} },
}
@action goBack() {
goBack() { this.replaceRoute(this.showRouteName, this.model.id);
this.replaceRoute(this.showRouteName, this.model.id); },
} },
} });

View File

@ -1,4 +1,4 @@
import { inject as service } from "@ember/service"; import { COMPONENTS, THEMES } from "admin/models/theme";
import { import {
empty, empty,
filterBy, filterBy,
@ -6,9 +6,8 @@ import {
match, match,
notEmpty, notEmpty,
} from "@ember/object/computed"; } from "@ember/object/computed";
import { COMPONENTS, THEMES } from "admin/models/theme";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import EmberObject, { action } from "@ember/object"; import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import ThemeSettings from "admin/models/theme-settings"; import ThemeSettings from "admin/models/theme-settings";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
@ -16,35 +15,31 @@ import { makeArray } from "discourse-common/lib/helpers";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { url } from "discourse/lib/computed"; import { url } from "discourse/lib/computed";
import { inject as service } from "@ember/service";
const THEME_UPLOAD_VAR = 2; const THEME_UPLOAD_VAR = 2;
export default class AdminCustomizeThemesShowController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
downloadUrl: url("model.id", "/admin/customize/themes/%@/export"),
editRouteName = "adminCustomizeThemes.edit"; previewUrl: url("model.id", "/admin/themes/%@/preview"),
addButtonDisabled: empty("selectedChildThemeId"),
@url("model.id", "/admin/customize/themes/%@/export") downloadUrl; editRouteName: "adminCustomizeThemes.edit",
@url("model.id", "/admin/themes/%@/preview") previewUrl; parentThemesNames: mapBy("model.parentThemes", "name"),
@empty("selectedChildThemeId") addButtonDisabled; availableParentThemes: filterBy("allThemes", "component", false),
@mapBy("model.parentThemes", "name") parentThemesNames; availableActiveParentThemes: filterBy("availableParentThemes", "isActive"),
@filterBy("allThemes", "component", false) availableParentThemes; availableThemesNames: mapBy("availableParentThemes", "name"),
@filterBy("availableParentThemes", "isActive") availableActiveParentThemes; availableActiveThemesNames: mapBy("availableActiveParentThemes", "name"),
@mapBy("availableParentThemes", "name") availableThemesNames; availableActiveChildThemes: filterBy("availableChildThemes", "hasParents"),
@mapBy("availableActiveParentThemes", "name") availableActiveThemesNames; availableComponentsNames: mapBy("availableChildThemes", "name"),
@filterBy("availableChildThemes", "hasParents") availableActiveChildThemes; availableActiveComponentsNames: mapBy("availableActiveChildThemes", "name"),
@mapBy("availableChildThemes", "name") availableComponentsNames; childThemesNames: mapBy("model.childThemes", "name"),
@mapBy("availableActiveChildThemes", "name") availableActiveComponentsNames; extraFiles: filterBy("model.theme_fields", "target", "extra_js"),
@mapBy("model.childThemes", "name") childThemesNames;
@filterBy("model.theme_fields", "target", "extra_js") extraFiles;
@notEmpty("settings") hasSettings;
@notEmpty("translations") hasTranslations;
@match("model.remote_theme.remote_url", /^http(s)?:\/\//) sourceIsHttp;
@discourseComputed("model.component", "model.remote_theme") @discourseComputed("model.component", "model.remote_theme")
showCheckboxes() { showCheckboxes() {
return !this.model.component || this.model.remote_theme; return !this.model.component || this.model.remote_theme;
} },
@discourseComputed("model.editedFields") @discourseComputed("model.editedFields")
editedFieldsFormatted() { editedFieldsFormatted() {
@ -62,13 +57,13 @@ export default class AdminCustomizeThemesShowController extends Controller {
descriptions.push(resultString); descriptions.push(resultString);
}); });
return descriptions; return descriptions;
} },
@discourseComputed("colorSchemeId", "model.color_scheme_id") @discourseComputed("colorSchemeId", "model.color_scheme_id")
colorSchemeChanged(colorSchemeId, existingId) { colorSchemeChanged(colorSchemeId, existingId) {
colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId, 10); colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId, 10);
return colorSchemeId !== existingId; return colorSchemeId !== existingId;
} },
@discourseComputed("availableChildThemes", "model.childThemes.[]", "model") @discourseComputed("availableChildThemes", "model.childThemes.[]", "model")
selectableChildThemes(available, childThemes) { selectableChildThemes(available, childThemes) {
@ -78,7 +73,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
: available.filter((theme) => !childThemes.includes(theme)); : available.filter((theme) => !childThemes.includes(theme));
return themes.length === 0 ? null : themes; return themes.length === 0 ? null : themes;
} }
} },
@discourseComputed("model.parentThemes.[]") @discourseComputed("model.parentThemes.[]")
relativesSelectorSettingsForComponent() { relativesSelectorSettingsForComponent() {
@ -96,7 +91,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
allThemes: this.allThemes, allThemes: this.allThemes,
setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all_themes"), setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all_themes"),
}); });
} },
@discourseComputed("model.parentThemes.[]") @discourseComputed("model.parentThemes.[]")
relativesSelectorSettingsForTheme() { relativesSelectorSettingsForTheme() {
@ -114,7 +109,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
allThemes: this.allThemes, allThemes: this.allThemes,
setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all"), setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all"),
}); });
} },
@discourseComputed("allThemes", "model.component", "model") @discourseComputed("allThemes", "model.component", "model")
availableChildThemes(allThemes) { availableChildThemes(allThemes) {
@ -124,36 +119,40 @@ export default class AdminCustomizeThemesShowController extends Controller {
(theme) => theme.get("id") !== themeId && theme.get("component") (theme) => theme.get("id") !== themeId && theme.get("component")
); );
} }
} },
@discourseComputed("model.component") @discourseComputed("model.component")
convertKey(component) { convertKey(component) {
const type = component ? "component" : "theme"; const type = component ? "component" : "theme";
return `admin.customize.theme.convert_${type}`; return `admin.customize.theme.convert_${type}`;
} },
@discourseComputed("model.component") @discourseComputed("model.component")
convertIcon(component) { convertIcon(component) {
return component ? "cube" : ""; return component ? "cube" : "";
} },
@discourseComputed("model.component") @discourseComputed("model.component")
convertTooltip(component) { convertTooltip(component) {
const type = component ? "component" : "theme"; const type = component ? "component" : "theme";
return `admin.customize.theme.convert_${type}_tooltip`; return `admin.customize.theme.convert_${type}_tooltip`;
} },
@discourseComputed("model.settings") @discourseComputed("model.settings")
settings(settings) { settings(settings) {
return settings.map((setting) => ThemeSettings.create(setting)); return settings.map((setting) => ThemeSettings.create(setting));
} },
hasSettings: notEmpty("settings"),
@discourseComputed("model.translations") @discourseComputed("model.translations")
translations(translations) { translations(translations) {
return translations.map((setting) => return translations.map((setting) =>
ThemeSettings.create({ ...setting, textarea: true }) ThemeSettings.create({ ...setting, textarea: true })
); );
} },
hasTranslations: notEmpty("translations"),
@discourseComputed( @discourseComputed(
"model.remote_theme.local_version", "model.remote_theme.local_version",
@ -162,12 +161,12 @@ export default class AdminCustomizeThemesShowController extends Controller {
) )
hasOverwrittenHistory(localVersion, remoteVersion, commitsBehind) { hasOverwrittenHistory(localVersion, remoteVersion, commitsBehind) {
return localVersion !== remoteVersion && commitsBehind === -1; return localVersion !== remoteVersion && commitsBehind === -1;
} },
@discourseComputed("model.remoteError", "updatingRemote") @discourseComputed("model.remoteError", "updatingRemote")
showRemoteError(errorMessage, updating) { showRemoteError(errorMessage, updating) {
return errorMessage && !updating; return errorMessage && !updating;
} },
@discourseComputed( @discourseComputed(
"model.remote_theme.remote_url", "model.remote_theme.remote_url",
@ -176,13 +175,13 @@ export default class AdminCustomizeThemesShowController extends Controller {
) )
finishInstall(remoteUrl, localVersion, commitsBehind) { finishInstall(remoteUrl, localVersion, commitsBehind) {
return remoteUrl && !localVersion && !commitsBehind; return remoteUrl && !localVersion && !commitsBehind;
} },
editedFieldsForTarget(target) { editedFieldsForTarget(target) {
return this.get("model.editedFields").filter( return this.get("model.editedFields").filter(
(field) => field.target === target (field) => field.target === target
); );
} },
commitSwitchType() { commitSwitchType() {
const model = this.model; const model = this.model;
@ -223,8 +222,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
}); });
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
transitionToEditRoute() { transitionToEditRoute() {
this.transitionToRoute( this.transitionToRoute(
this.editRouteName, this.editRouteName,
@ -232,7 +230,8 @@ export default class AdminCustomizeThemesShowController extends Controller {
"common", "common",
"scss" "scss"
); );
} },
sourceIsHttp: match("model.remote_theme.remote_url", /^http(s)?:\/\//),
@discourseComputed( @discourseComputed(
"model.remote_theme.remote_url", "model.remote_theme.remote_url",
@ -242,186 +241,168 @@ export default class AdminCustomizeThemesShowController extends Controller {
return remoteThemeBranch return remoteThemeBranch
? `${remoteThemeUrl.replace(/\.git$/, "")}/tree/${remoteThemeBranch}` ? `${remoteThemeUrl.replace(/\.git$/, "")}/tree/${remoteThemeBranch}`
: remoteThemeUrl; : remoteThemeUrl;
} },
@discourseComputed("model.user.id", "model.default") @discourseComputed("model.user.id", "model.default")
showConvert(userId, defaultTheme) { showConvert(userId, defaultTheme) {
return userId > 0 && !defaultTheme; return userId > 0 && !defaultTheme;
} },
@action actions: {
updateToLatest() { updateToLatest() {
this.set("updatingRemote", true); this.set("updatingRemote", true);
this.model this.model
.updateToLatest() .updateToLatest()
.catch(popupAjaxError) .catch(popupAjaxError)
.finally(() => { .finally(() => {
this.set("updatingRemote", false); this.set("updatingRemote", false);
});
},
checkForThemeUpdates() {
this.set("updatingRemote", true);
this.model
.checkForUpdates()
.catch(popupAjaxError)
.finally(() => {
this.set("updatingRemote", false);
});
},
addUploadModal() {
showModal("admin-add-upload", { admin: true, name: "" });
},
addUpload(info) {
let model = this.model;
model.setField("common", info.name, "", info.upload_id, THEME_UPLOAD_VAR);
model.saveChanges("theme_fields").catch((e) => popupAjaxError(e));
},
cancelChangeScheme() {
this.set("colorSchemeId", this.get("model.color_scheme_id"));
},
changeScheme() {
let schemeId = this.colorSchemeId;
this.set(
"model.color_scheme_id",
schemeId === null ? null : parseInt(schemeId, 10)
);
this.model.saveChanges("color_scheme_id");
},
startEditingName() {
this.set("oldName", this.get("model.name"));
this.set("editingName", true);
},
cancelEditingName() {
this.set("model.name", this.oldName);
this.set("editingName", false);
},
finishedEditingName() {
this.model.saveChanges("name");
this.set("editingName", false);
},
editTheme() {
if (this.get("model.remote_theme.is_git")) {
this.dialog.confirm({
message: I18n.t("admin.customize.theme.edit_confirm"),
didConfirm: () => this.transitionToEditRoute(),
});
} else {
this.transitionToEditRoute();
}
},
applyDefault() {
const model = this.model;
model.saveChanges("default").then(() => {
if (model.get("default")) {
this.allThemes.forEach((theme) => {
if (theme !== model && theme.get("default")) {
theme.set("default", false);
}
});
}
}); });
} },
@action applyUserSelectable() {
checkForThemeUpdates() { this.model.saveChanges("user_selectable");
this.set("updatingRemote", true); },
this.model
.checkForUpdates() applyAutoUpdateable() {
.catch(popupAjaxError) this.model.saveChanges("auto_update");
.finally(() => { },
this.set("updatingRemote", false);
addChildTheme() {
let themeId = parseInt(this.selectedChildThemeId, 10);
let theme = this.allThemes.findBy("id", themeId);
this.model.addChildTheme(theme).then(() => this.store.findAll("theme"));
},
removeUpload(upload) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.theme.delete_upload_confirm"),
didConfirm: () => this.model.removeField(upload),
}); });
} },
@action removeChildTheme(theme) {
addUploadModal() { this.model
showModal("admin-add-upload", { admin: true, name: "" }); .removeChildTheme(theme)
} .then(() => this.store.findAll("theme"));
},
@action destroy() {
addUpload(info) { return this.dialog.yesNoConfirm({
let model = this.model; message: I18n.t("admin.customize.delete_confirm", {
model.setField("common", info.name, "", info.upload_id, THEME_UPLOAD_VAR); theme_name: this.get("model.name"),
model.saveChanges("theme_fields").catch((e) => popupAjaxError(e)); }),
} didConfirm: () => {
const model = this.model;
@action model.setProperties({ recentlyInstalled: false });
cancelChangeScheme() { model.destroyRecord().then(() => {
this.set("colorSchemeId", this.get("model.color_scheme_id")); this.allThemes.removeObject(model);
} this.transitionToRoute("adminCustomizeThemes");
});
@action },
changeScheme() {
let schemeId = this.colorSchemeId;
this.set(
"model.color_scheme_id",
schemeId === null ? null : parseInt(schemeId, 10)
);
this.model.saveChanges("color_scheme_id");
}
@action
startEditingName() {
this.set("oldName", this.get("model.name"));
this.set("editingName", true);
}
@action
cancelEditingName() {
this.set("model.name", this.oldName);
this.set("editingName", false);
}
@action
finishedEditingName() {
this.model.saveChanges("name");
this.set("editingName", false);
}
@action
editTheme() {
if (this.get("model.remote_theme.is_git")) {
this.dialog.confirm({
message: I18n.t("admin.customize.theme.edit_confirm"),
didConfirm: () => this.transitionToEditRoute(),
}); });
} else { },
this.transitionToEditRoute();
}
}
@action switchType() {
applyDefault() { const relatives = this.get("model.component")
const model = this.model; ? this.get("model.parentThemes")
model.saveChanges("default").then(() => { : this.get("model.childThemes");
if (model.get("default")) {
this.allThemes.forEach((theme) => { let message = I18n.t(`${this.convertKey}_alert_generic`);
if (theme !== model && theme.get("default")) {
theme.set("default", false); if (relatives && relatives.length > 0) {
} message = I18n.t(`${this.convertKey}_alert`, {
relatives: relatives
.map((relative) => relative.get("name"))
.join(", "),
}); });
} }
});
}
@action return this.dialog.yesNoConfirm({
applyUserSelectable() { message,
this.model.saveChanges("user_selectable"); didConfirm: () => this.commitSwitchType(),
}
@action
applyAutoUpdateable() {
this.model.saveChanges("auto_update");
}
@action
addChildTheme() {
let themeId = parseInt(this.selectedChildThemeId, 10);
let theme = this.allThemes.findBy("id", themeId);
this.model.addChildTheme(theme).then(() => this.store.findAll("theme"));
}
@action
removeUpload(upload) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.theme.delete_upload_confirm"),
didConfirm: () => this.model.removeField(upload),
});
}
@action
removeChildTheme(theme) {
this.model.removeChildTheme(theme).then(() => this.store.findAll("theme"));
}
@action
destroyTheme() {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.delete_confirm", {
theme_name: this.get("model.name"),
}),
didConfirm: () => {
const model = this.model;
model.setProperties({ recentlyInstalled: false });
model.destroyRecord().then(() => {
this.allThemes.removeObject(model);
this.transitionToRoute("adminCustomizeThemes");
});
},
});
}
@action
switchType() {
const relatives = this.get("model.component")
? this.get("model.parentThemes")
: this.get("model.childThemes");
let message = I18n.t(`${this.convertKey}_alert_generic`);
if (relatives && relatives.length > 0) {
message = I18n.t(`${this.convertKey}_alert`, {
relatives: relatives.map((relative) => relative.get("name")).join(", "),
}); });
} },
return this.dialog.yesNoConfirm({ enableComponent() {
message, this.model.set("enabled", true);
didConfirm: () => this.commitSwitchType(), this.model
}); .saveChanges("enabled")
} .catch(() => this.model.set("enabled", false));
},
@action disableComponent() {
enableComponent() { this.model.set("enabled", false);
this.model.set("enabled", true); this.model
this.model .saveChanges("enabled")
.saveChanges("enabled") .catch(() => this.model.set("enabled", true));
.catch(() => this.model.set("enabled", false)); },
} },
});
@action
disableComponent() {
this.model.set("enabled", false);
this.model
.saveChanges("enabled")
.catch(() => this.model.set("enabled", true));
}
}

View File

@ -2,21 +2,21 @@ import Controller from "@ember/controller";
import { THEMES } from "admin/models/theme"; import { THEMES } from "admin/models/theme";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default class AdminCustomizeThemesController extends Controller { export default Controller.extend({
currentTab = THEMES; currentTab: THEMES,
@discourseComputed("model", "model.@each.component") @discourseComputed("model", "model.@each.component")
fullThemes(themes) { fullThemes(themes) {
return themes.filter((t) => !t.get("component")); return themes.filter((t) => !t.get("component"));
} },
@discourseComputed("model", "model.@each.component") @discourseComputed("model", "model.@each.component")
childThemes(themes) { childThemes(themes) {
return themes.filter((t) => t.get("component")); return themes.filter((t) => t.get("component"));
} },
@discourseComputed("model.content") @discourseComputed("model.content")
installedThemes(content) { installedThemes(content) {
return content || []; return content || [];
} },
} });

View File

@ -1,9 +1,9 @@
import { computed } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import AdminDashboard from "admin/models/admin-dashboard"; import AdminDashboard from "admin/models/admin-dashboard";
import I18n from "I18n"; import I18n from "I18n";
import PeriodComputationMixin from "admin/mixins/period-computation"; import PeriodComputationMixin from "admin/mixins/period-computation";
import Report from "admin/models/report"; import Report from "admin/models/report";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { makeArray } from "discourse-common/lib/helpers"; import { makeArray } from "discourse-common/lib/helpers";
@ -15,49 +15,41 @@ function staticReport(reportType) {
}); });
} }
export default class AdminDashboardGeneralController extends Controller.extend( export default Controller.extend(PeriodComputationMixin, {
PeriodComputationMixin isLoading: false,
) { dashboardFetchedAt: null,
@controller("exception") exceptionController; exceptionController: controller("exception"),
logSearchQueriesEnabled: setting("log_search_queries"),
isLoading = false;
dashboardFetchedAt = null;
@setting("log_search_queries") logSearchQueriesEnabled;
@staticReport("users_by_type") usersByTypeReport;
@staticReport("users_by_trust_level") usersByTrustLevelReport;
@staticReport("storage_report") storageReport;
@discourseComputed("siteSettings.dashboard_general_tab_activity_metrics") @discourseComputed("siteSettings.dashboard_general_tab_activity_metrics")
activityMetrics(metrics) { activityMetrics(metrics) {
return (metrics || "").split("|").filter(Boolean); return (metrics || "").split("|").filter(Boolean);
} },
@computed("siteSettings.dashboard_hidden_reports") hiddenReports: computed("siteSettings.dashboard_hidden_reports", function () {
get hiddenReports() {
return (this.siteSettings.dashboard_hidden_reports || "") return (this.siteSettings.dashboard_hidden_reports || "")
.split("|") .split("|")
.filter(Boolean); .filter(Boolean);
} }),
@computed("activityMetrics", "hiddenReports") isActivityMetricsVisible: computed(
get isActivityMetricsVisible() { "activityMetrics",
return ( "hiddenReports",
this.activityMetrics.length && function () {
this.activityMetrics.some((x) => !this.hiddenReports.includes(x)) return (
); this.activityMetrics.length &&
} this.activityMetrics.some((x) => !this.hiddenReports.includes(x))
);
}
),
@computed("hiddenReports") isSearchReportsVisible: computed("hiddenReports", function () {
get isSearchReportsVisible() {
return ["top_referred_topics", "trending_search"].some( return ["top_referred_topics", "trending_search"].some(
(x) => !this.hiddenReports.includes(x) (x) => !this.hiddenReports.includes(x)
); );
} }),
@computed("hiddenReports") isCommunityHealthVisible: computed("hiddenReports", function () {
get isCommunityHealthVisible() {
return [ return [
"consolidated_page_views", "consolidated_page_views",
"signups", "signups",
@ -67,7 +59,7 @@ export default class AdminDashboardGeneralController extends Controller.extend(
"daily_engaged_users", "daily_engaged_users",
"new_contributors", "new_contributors",
].some((x) => !this.hiddenReports.includes(x)); ].some((x) => !this.hiddenReports.includes(x));
} }),
@discourseComputed @discourseComputed
activityMetricsFilters() { activityMetricsFilters() {
@ -75,14 +67,14 @@ export default class AdminDashboardGeneralController extends Controller.extend(
startDate: this.lastMonth, startDate: this.lastMonth,
endDate: this.today, endDate: this.today,
}; };
} },
@discourseComputed @discourseComputed
topReferredTopicsOptions() { topReferredTopicsOptions() {
return { return {
table: { total: false, limit: 8 }, table: { total: false, limit: 8 },
}; };
} },
@discourseComputed @discourseComputed
topReferredTopicsFilters() { topReferredTopicsFilters() {
@ -90,7 +82,7 @@ export default class AdminDashboardGeneralController extends Controller.extend(
startDate: moment().subtract(6, "days").startOf("day"), startDate: moment().subtract(6, "days").startOf("day"),
endDate: this.today, endDate: this.today,
}; };
} },
@discourseComputed @discourseComputed
trendingSearchFilters() { trendingSearchFilters() {
@ -98,21 +90,25 @@ export default class AdminDashboardGeneralController extends Controller.extend(
startDate: moment().subtract(1, "month").startOf("day"), startDate: moment().subtract(1, "month").startOf("day"),
endDate: this.today, endDate: this.today,
}; };
} },
@discourseComputed @discourseComputed
trendingSearchOptions() { trendingSearchOptions() {
return { return {
table: { total: false, limit: 8 }, table: { total: false, limit: 8 },
}; };
} },
@discourseComputed @discourseComputed
trendingSearchDisabledLabel() { trendingSearchDisabledLabel() {
return I18n.t("admin.dashboard.reports.trending_search.disabled", { return I18n.t("admin.dashboard.reports.trending_search.disabled", {
basePath: getURL(""), basePath: getURL(""),
}); });
} },
usersByTypeReport: staticReport("users_by_type"),
usersByTrustLevelReport: staticReport("users_by_trust_level"),
storageReport: staticReport("storage_report"),
fetchDashboard() { fetchDashboard() {
if (this.isLoading) { if (this.isLoading) {
@ -141,14 +137,14 @@ export default class AdminDashboardGeneralController extends Controller.extend(
}) })
.finally(() => this.set("isLoading", false)); .finally(() => this.set("isLoading", false));
} }
} },
@discourseComputed("startDate", "endDate") @discourseComputed("startDate", "endDate")
filters(startDate, endDate) { filters(startDate, endDate) {
return { startDate, endDate }; return { startDate, endDate };
} },
_reportsForPeriodURL(period) { _reportsForPeriodURL(period) {
return getURL(`/admin?period=${period}`); return getURL(`/admin?period=${period}`);
} },
} });

View File

@ -1,12 +1,10 @@
import { computed } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import PeriodComputationMixin from "admin/mixins/period-computation"; import PeriodComputationMixin from "admin/mixins/period-computation";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
export default class AdminDashboardModerationController extends Controller.extend( export default Controller.extend(PeriodComputationMixin, {
PeriodComputationMixin
) {
@discourseComputed @discourseComputed
flagsStatusOptions() { flagsStatusOptions() {
return { return {
@ -15,15 +13,17 @@ export default class AdminDashboardModerationController extends Controller.exten
perPage: 10, perPage: 10,
}, },
}; };
} },
@computed("siteSettings.dashboard_hidden_reports") isModeratorsActivityVisible: computed(
get isModeratorsActivityVisible() { "siteSettings.dashboard_hidden_reports",
return !(this.siteSettings.dashboard_hidden_reports || "") function () {
.split("|") return !(this.siteSettings.dashboard_hidden_reports || "")
.filter(Boolean) .split("|")
.includes("moderators_activity"); .filter(Boolean)
} .includes("moderators_activity");
}
),
@discourseComputed @discourseComputed
userFlaggingRatioOptions() { userFlaggingRatioOptions() {
@ -33,19 +33,19 @@ export default class AdminDashboardModerationController extends Controller.exten
perPage: 10, perPage: 10,
}, },
}; };
} },
@discourseComputed("startDate", "endDate") @discourseComputed("startDate", "endDate")
filters(startDate, endDate) { filters(startDate, endDate) {
return { startDate, endDate }; return { startDate, endDate };
} },
@discourseComputed("lastWeek", "endDate") @discourseComputed("lastWeek", "endDate")
lastWeekfilters(startDate, endDate) { lastWeekfilters(startDate, endDate) {
return { startDate, endDate }; return { startDate, endDate };
} },
_reportsForPeriodURL(period) { _reportsForPeriodURL(period) {
return getURL(`/admin/dashboard/moderation?period=${period}`); return getURL(`/admin/dashboard/moderation?period=${period}`);
} },
} });

View File

@ -2,10 +2,10 @@ import Controller from "@ember/controller";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { action, get } from "@ember/object"; import { get } from "@ember/object";
export default class AdminDashboardReportsController extends Controller { export default Controller.extend({
filter = null; filter: null,
@discourseComputed( @discourseComputed(
"model.[]", "model.[]",
@ -29,14 +29,15 @@ export default class AdminDashboardReportsController extends Controller {
reports = reports.filter((report) => !hiddenReports.includes(report.type)); reports = reports.filter((report) => !hiddenReports.includes(report.type));
return reports; return reports;
} },
@action actions: {
filterReports(filter) { filterReports(filter) {
discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY); discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY);
} },
},
_performFiltering(filter) { _performFiltering(filter) {
this.set("filter", filter); this.set("filter", filter);
} },
} });

View File

@ -1,19 +1,17 @@
import { action, computed } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import AdminDashboard from "admin/models/admin-dashboard"; import AdminDashboard from "admin/models/admin-dashboard";
import VersionCheck from "admin/models/version-check"; import VersionCheck from "admin/models/version-check";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { setting } from "discourse/lib/computed"; import { setting } from "discourse/lib/computed";
const PROBLEMS_CHECK_MINUTES = 1; const PROBLEMS_CHECK_MINUTES = 1;
export default class AdminDashboardController extends Controller { export default Controller.extend({
@controller("exception") exceptionController; isLoading: false,
dashboardFetchedAt: null,
isLoading = false; exceptionController: controller("exception"),
dashboardFetchedAt = null; showVersionChecks: setting("version_checks"),
@setting("version_checks") showVersionChecks;
@discourseComputed( @discourseComputed(
"lowPriorityProblems.length", "lowPriorityProblems.length",
@ -23,29 +21,25 @@ export default class AdminDashboardController extends Controller {
const problemsLength = const problemsLength =
lowPriorityProblemsLength + highPriorityProblemsLength; lowPriorityProblemsLength + highPriorityProblemsLength;
return this.currentUser.admin && problemsLength > 0; return this.currentUser.admin && problemsLength > 0;
} },
@computed("siteSettings.dashboard_visible_tabs") visibleTabs: computed("siteSettings.dashboard_visible_tabs", function () {
get visibleTabs() {
return (this.siteSettings.dashboard_visible_tabs || "") return (this.siteSettings.dashboard_visible_tabs || "")
.split("|") .split("|")
.filter(Boolean); .filter(Boolean);
} }),
@computed("visibleTabs") isModerationTabVisible: computed("visibleTabs", function () {
get isModerationTabVisible() {
return this.visibleTabs.includes("moderation"); return this.visibleTabs.includes("moderation");
} }),
@computed("visibleTabs") isSecurityTabVisible: computed("visibleTabs", function () {
get isSecurityTabVisible() {
return this.visibleTabs.includes("security"); return this.visibleTabs.includes("security");
} }),
@computed("visibleTabs") isReportsTabVisible: computed("visibleTabs", function () {
get isReportsTabVisible() {
return this.visibleTabs.includes("reports"); return this.visibleTabs.includes("reports");
} }),
fetchProblems() { fetchProblems() {
if (this.isLoadingProblems) { if (this.isLoadingProblems) {
@ -59,7 +53,7 @@ export default class AdminDashboardController extends Controller {
) { ) {
this._loadProblems(); this._loadProblems();
} }
} },
fetchDashboard() { fetchDashboard() {
const versionChecks = this.siteSettings.version_checks; const versionChecks = this.siteSettings.version_checks;
@ -94,7 +88,7 @@ export default class AdminDashboardController extends Controller {
this.set("isLoading", false); this.set("isLoading", false);
}); });
} }
} },
_loadProblems() { _loadProblems() {
this.setProperties({ this.setProperties({
@ -114,15 +108,16 @@ export default class AdminDashboardController extends Controller {
); );
}) })
.finally(() => this.set("loadingProblems", false)); .finally(() => this.set("loadingProblems", false));
} },
@discourseComputed("problemsFetchedAt") @discourseComputed("problemsFetchedAt")
problemsTimestamp(problemsFetchedAt) { problemsTimestamp(problemsFetchedAt) {
return moment(problemsFetchedAt).locale("en").format("LLL"); return moment(problemsFetchedAt).locale("en").format("LLL");
} },
@action actions: {
refreshProblems() { refreshProblems() {
this._loadProblems(); this._loadProblems();
} },
} },
});

View File

@ -1,31 +1,31 @@
import { action } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminEmailAdvancedTestController extends Controller { export default Controller.extend({
email = null; email: null,
text = null; text: null,
elided = null; elided: null,
format = null; format: null,
loading = null; loading: null,
@action actions: {
run() { run() {
this.set("loading", true); this.set("loading", true);
ajax("/admin/email/advanced-test", { ajax("/admin/email/advanced-test", {
type: "POST", type: "POST",
data: { email: this.email }, data: { email: this.email },
})
.then((data) => {
this.setProperties({
text: data.text,
elided: data.elided,
format: data.format,
});
}) })
.catch(popupAjaxError) .then((data) => {
.finally(() => this.set("loading", false)); this.setProperties({
} text: data.text,
} elided: data.elided,
format: data.format,
});
})
.catch(popupAjaxError)
.finally(() => this.set("loading", false));
},
},
});

View File

@ -1,18 +1,18 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object"; import { action } from "@ember/object";
export default class AdminEmailBouncedController extends AdminEmailLogsController { export default AdminEmailLogsController.extend({
@action @action
handleShowIncomingEmail(id, event) { handleShowIncomingEmail(id, event) {
event?.preventDefault(); event?.preventDefault();
this.send("showIncomingEmail", id); this.send("showIncomingEmail", id);
} },
@observes("filter.{status,user,address,type}") @observes("filter.{status,user,address,type}")
filterEmailLogs() { filterEmailLogs() {
discourseDebounce(this, this.loadLogs, INPUT_DELAY); discourseDebounce(this, this.loadLogs, INPUT_DELAY);
} },
} });

View File

@ -1,22 +1,21 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { empty } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { observes } from "@ember-decorators/object"; import { empty } from "@ember/object/computed";
import { observes } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
export default class AdminEmailIndexController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
/** /**
Is the "send test email" button disabled? Is the "send test email" button disabled?
@property sendTestEmailDisabled @property sendTestEmailDisabled
**/ **/
@empty("testEmailAddress") sendTestEmailDisabled; sendTestEmailDisabled: empty("testEmailAddress"),
/** /**
Clears the 'sentTestEmail' property on successful send. Clears the 'sentTestEmail' property on successful send.
@ -26,40 +25,43 @@ export default class AdminEmailIndexController extends Controller {
@observes("testEmailAddress") @observes("testEmailAddress")
testEmailAddressChanged() { testEmailAddressChanged() {
this.set("sentTestEmail", false); this.set("sentTestEmail", false);
} },
/** actions: {
Sends a test email to the currently entered email address /**
Sends a test email to the currently entered email address
@method sendTestEmail @method sendTestEmail
**/ **/
@action sendTestEmail() {
sendTestEmail() { this.setProperties({
this.setProperties({ sendingEmail: true,
sendingEmail: true, sentTestEmail: false,
sentTestEmail: false, });
});
ajax("/admin/email/test", { ajax("/admin/email/test", {
type: "POST", type: "POST",
data: { email_address: this.testEmailAddress }, data: { email_address: this.testEmailAddress },
})
.then((response) =>
this.set("sentTestEmailMessage", response.sent_test_email_message)
)
.catch((e) => {
if (e.jqXHR.responseJSON?.errors) {
this.dialog.alert({
message: htmlSafe(
I18n.t("admin.email.error", {
server_error: escapeExpression(e.jqXHR.responseJSON.errors[0]),
})
),
});
} else {
this.dialog.alert({ message: I18n.t("admin.email.test_error") });
}
}) })
.finally(() => this.set("sendingEmail", false)); .then((response) =>
} this.set("sentTestEmailMessage", response.sent_test_email_message)
} )
.catch((e) => {
if (e.jqXHR.responseJSON?.errors) {
this.dialog.alert({
message: htmlSafe(
I18n.t("admin.email.error", {
server_error: escapeExpression(
e.jqXHR.responseJSON.errors[0]
),
})
),
});
} else {
this.dialog.alert({ message: I18n.t("admin.email.test_error") });
}
})
.finally(() => this.set("sendingEmail", false));
},
},
});

View File

@ -1,11 +1,14 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import EmailLog from "admin/models/email-log"; import EmailLog from "admin/models/email-log";
import EmberObject, { action } from "@ember/object"; import EmberObject from "@ember/object";
export default class AdminEmailLogsController extends Controller { export default Controller.extend({
loading = false; loading: false,
filter = EmberObject.create();
init() {
this._super(...arguments);
this.set("filter", EmberObject.create());
},
loadLogs(sourceModel, loadMore) { loadLogs(sourceModel, loadMore) {
if ((loadMore && this.loading) || this.get("model.allLoaded")) { if ((loadMore && this.loading) || this.get("model.allLoaded")) {
return; return;
@ -35,10 +38,11 @@ export default class AdminEmailLogsController extends Controller {
} }
}) })
.finally(() => this.set("loading", false)); .finally(() => this.set("loading", false));
} },
@action actions: {
loadMore() { loadMore() {
this.loadLogs(EmailLog, true); this.loadLogs(EmailLog, true);
} },
} },
});

View File

@ -1,67 +1,66 @@
import { inject as service } from "@ember/service";
import { empty, notEmpty, or } from "@ember/object/computed"; import { empty, notEmpty, or } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import EmailPreview from "admin/models/email-preview"; import EmailPreview from "admin/models/email-preview";
import { action, get } from "@ember/object"; import { action, get } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminEmailPreviewDigestController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
username: null,
username = null; lastSeen: null,
lastSeen = null; emailEmpty: empty("email"),
sendEmailDisabled: or("emailEmpty", "sendingEmail"),
@empty("email") emailEmpty; showSendEmailForm: notEmpty("model.html_content"),
@or("emailEmpty", "sendingEmail") sendEmailDisabled; htmlEmpty: empty("model.html_content"),
@notEmpty("model.html_content") showSendEmailForm;
@empty("model.html_content") htmlEmpty;
@action @action
toggleShowHtml(event) { toggleShowHtml(event) {
event?.preventDefault(); event?.preventDefault();
this.toggleProperty("showHtml"); this.toggleProperty("showHtml");
} },
@action actions: {
updateUsername(selected) { updateUsername(selected) {
this.set("username", get(selected, "firstObject")); this.set("username", get(selected, "firstObject"));
} },
@action refresh() {
refresh() { const model = this.model;
const model = this.model;
this.set("loading", true); this.set("loading", true);
this.set("sentEmail", false); this.set("sentEmail", false);
let username = this.username; let username = this.username;
if (!username) { if (!username) {
username = this.currentUser.get("username"); username = this.currentUser.get("username");
this.set("username", username); this.set("username", username);
} }
EmailPreview.findDigest(username, this.lastSeen).then((email) => { EmailPreview.findDigest(username, this.lastSeen).then((email) => {
model.setProperties(email.getProperties("html_content", "text_content")); model.setProperties(
this.set("loading", false); email.getProperties("html_content", "text_content")
}); );
} this.set("loading", false);
@action
sendEmail() {
this.set("sendingEmail", true);
this.set("sentEmail", false);
EmailPreview.sendDigest(this.username, this.lastSeen, this.email)
.then((result) => {
if (result.errors) {
this.dialog.alert(result.errors);
} else {
this.set("sentEmail", true);
}
})
.catch(popupAjaxError)
.finally(() => {
this.set("sendingEmail", false);
}); });
} },
}
sendEmail() {
this.set("sendingEmail", true);
this.set("sentEmail", false);
EmailPreview.sendDigest(this.username, this.lastSeen, this.email)
.then((result) => {
if (result.errors) {
this.dialog.alert(result.errors);
} else {
this.set("sentEmail", true);
}
})
.catch(popupAjaxError)
.finally(() => {
this.set("sendingEmail", false);
});
},
},
});

View File

@ -1,18 +1,18 @@
import { action } from "@ember/object";
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import IncomingEmail from "admin/models/incoming-email"; import IncomingEmail from "admin/models/incoming-email";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
export default class AdminEmailReceivedController extends AdminEmailLogsController { export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject}") @observes("filter.{status,from,to,subject}")
filterIncomingEmails() { filterIncomingEmails() {
discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY); discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY);
} },
@action actions: {
loadMore() { loadMore() {
this.loadLogs(IncomingEmail, true); this.loadLogs(IncomingEmail, true);
} },
} },
});

View File

@ -2,23 +2,24 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import IncomingEmail from "admin/models/incoming-email"; import IncomingEmail from "admin/models/incoming-email";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object"; import { action } from "@ember/object";
export default class AdminEmailRejectedController extends AdminEmailLogsController { export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject,error}") @observes("filter.{status,from,to,subject,error}")
filterIncomingEmails() { filterIncomingEmails() {
discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY); discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY);
} },
@action @action
handleShowIncomingEmail(id, event) { handleShowIncomingEmail(id, event) {
event?.preventDefault(); event?.preventDefault();
this.send("showIncomingEmail", id); this.send("showIncomingEmail", id);
} },
@action actions: {
loadMore() { loadMore() {
this.loadLogs(IncomingEmail, true); this.loadLogs(IncomingEmail, true);
} },
} },
});

View File

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
export default class AdminEmailSentController extends AdminEmailLogsController { export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type,reply_key}") @observes("filter.{status,user,address,type,reply_key}")
filterEmailLogs() { filterEmailLogs() {
discourseDebounce(this, this.loadLogs, INPUT_DELAY); discourseDebounce(this, this.loadLogs, INPUT_DELAY);
} },
} });

View File

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
export default class AdminEmailSkippedController extends AdminEmailLogsController { export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type}") @observes("filter.{status,user,address,type}")
filterEmailLogs() { filterEmailLogs() {
discourseDebounce(this, this.loadLogs, INPUT_DELAY); discourseDebounce(this, this.loadLogs, INPUT_DELAY);
} },
} });

View File

@ -1,18 +1,17 @@
import { action } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminEmbeddingController extends Controller { export default Controller.extend({
saved = false; saved: false,
embedding = null; embedding: null,
// show settings if we have at least one created host // show settings if we have at least one created host
@discourseComputed("embedding.embeddable_hosts.@each.isCreated") @discourseComputed("embedding.embeddable_hosts.@each.isCreated")
showSecondary() { showSecondary() {
const hosts = this.get("embedding.embeddable_hosts"); const hosts = this.get("embedding.embeddable_hosts");
return hosts.length && hosts.findBy("isCreated"); return hosts.length && hosts.findBy("isCreated");
} },
@discourseComputed("embedding.base_url") @discourseComputed("embedding.base_url")
embeddingCode(baseUrl) { embeddingCode(baseUrl) {
@ -34,28 +33,27 @@ export default class AdminEmbeddingController extends Controller {
</script>`; </script>`;
return html; return html;
} },
@action actions: {
saveChanges() { saveChanges() {
const embedding = this.embedding; const embedding = this.embedding;
const updates = embedding.getProperties(embedding.get("fields")); const updates = embedding.getProperties(embedding.get("fields"));
this.set("saved", false); this.set("saved", false);
this.embedding this.embedding
.update(updates) .update(updates)
.then(() => this.set("saved", true)) .then(() => this.set("saved", true))
.catch(popupAjaxError); .catch(popupAjaxError);
} },
@action addHost() {
addHost() { const host = this.store.createRecord("embeddable-host");
const host = this.store.createRecord("embeddable-host"); this.get("embedding.embeddable_hosts").pushObject(host);
this.get("embedding.embeddable_hosts").pushObject(host); },
}
@action deleteHost(host) {
deleteHost(host) { this.get("embedding.embeddable_hosts").removeObject(host);
this.get("embedding.embeddable_hosts").removeObject(host); },
} },
} });

View File

@ -1,46 +1,49 @@
import { inject as service } from "@ember/service";
import { sort } from "@ember/object/computed";
import EmberObject, { action, computed } from "@ember/object"; import EmberObject, { action, computed } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { sort } from "@ember/object/computed";
import { inject as service } from "@ember/service";
const ALL_FILTER = "all"; const ALL_FILTER = "all";
export default class AdminEmojisController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
filter: null,
sorting: null,
filter = null;
sorting = null;
@sort("filteredEmojis.[]", "sorting") sortedEmojis;
init() { init() {
super.init(...arguments); this._super(...arguments);
this.setProperties({ this.setProperties({
filter: ALL_FILTER, filter: ALL_FILTER,
sorting: ["group", "name"], sorting: ["group", "name"],
}); });
} },
@computed("model") sortedEmojis: sort("filteredEmojis.[]", "sorting"),
get emojiGroups() {
return this.model.mapBy("group").uniq();
}
@computed("emojiGroups.[]") emojiGroups: computed("model", {
get sortingGroups() { get() {
return [ALL_FILTER].concat(this.emojiGroups); return this.model.mapBy("group").uniq();
} },
}),
@computed("model.[]", "filter") sortingGroups: computed("emojiGroups.[]", {
get filteredEmojis() { get() {
if (!this.filter || this.filter === ALL_FILTER) { return [ALL_FILTER].concat(this.emojiGroups);
return this.model; },
} else { }),
return this.model.filterBy("group", this.filter);
} filteredEmojis: computed("model.[]", "filter", {
} get() {
if (!this.filter || this.filter === ALL_FILTER) {
return this.model;
} else {
return this.model.filterBy("group", this.filter);
}
},
}),
_highlightEmojiList() { _highlightEmojiList() {
const customEmojiListEl = document.querySelector("#custom_emoji"); const customEmojiListEl = document.querySelector("#custom_emoji");
@ -53,12 +56,12 @@ export default class AdminEmojisController extends Controller {
customEmojiListEl.classList.remove("highlighted"); customEmojiListEl.classList.remove("highlighted");
}); });
} }
} },
@action @action
filterGroups(value) { filterGroups(value) {
this.set("filter", value); this.set("filter", value);
} },
@action @action
emojiUploaded(emoji, group) { emojiUploaded(emoji, group) {
@ -66,7 +69,7 @@ export default class AdminEmojisController extends Controller {
emoji.group = group; emoji.group = group;
this.model.pushObject(EmberObject.create(emoji)); this.model.pushObject(EmberObject.create(emoji));
this._highlightEmojiList(); this._highlightEmojiList();
} },
@action @action
destroyEmoji(emoji) { destroyEmoji(emoji) {
@ -82,5 +85,5 @@ export default class AdminEmojisController extends Controller {
}); });
}, },
}); });
} },
} });

View File

@ -1,24 +1,23 @@
import { action } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ScreenedEmail from "admin/models/screened-email"; import ScreenedEmail from "admin/models/screened-email";
import { exportEntity } from "discourse/lib/export-csv"; import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result"; import { outputExportResult } from "discourse/lib/export-result";
export default class AdminLogsScreenedEmailsController extends Controller { export default Controller.extend({
loading = false; loading: false,
@action actions: {
clearBlock(row) { clearBlock(row) {
row.clearBlock().then(function () { row.clearBlock().then(function () {
// feeling lazy // feeling lazy
window.location.reload(); window.location.reload();
}); });
} },
@action exportScreenedEmailList() {
exportScreenedEmailList() { exportEntity("screened_email").then(outputExportResult);
exportEntity("screened_email").then(outputExportResult); },
} },
show() { show() {
this.set("loading", true); this.set("loading", true);
@ -26,5 +25,5 @@ export default class AdminLogsScreenedEmailsController extends Controller {
this.set("model", result); this.set("model", result);
this.set("loading", false); this.set("loading", false);
}); });
} },
} });

View File

@ -1,32 +1,31 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import ScreenedIpAddress from "admin/models/screened-ip-address"; import ScreenedIpAddress from "admin/models/screened-ip-address";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { exportEntity } from "discourse/lib/export-csv"; import { exportEntity } from "discourse/lib/export-csv";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
import { outputExportResult } from "discourse/lib/export-result"; import { outputExportResult } from "discourse/lib/export-result";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class AdminLogsScreenedIpAddressesController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
loading: false,
loading = false; filter: null,
filter = null; savedIpAddress: null,
savedIpAddress = null;
_debouncedShow() { _debouncedShow() {
this.set("loading", true); this.set("loading", true);
ScreenedIpAddress.findAll(this.filter).then((result) => { ScreenedIpAddress.findAll(this.filter).then((result) => {
this.setProperties({ model: result, loading: false }); this.setProperties({ model: result, loading: false });
}); });
} },
@observes("filter") @observes("filter")
show() { show() {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY); discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
} },
@action @action
edit(record, event) { edit(record, event) {
@ -35,86 +34,81 @@ export default class AdminLogsScreenedIpAddressesController extends Controller {
this.set("savedIpAddress", record.get("ip_address")); this.set("savedIpAddress", record.get("ip_address"));
} }
record.set("editing", true); record.set("editing", true);
} },
@action actions: {
allow(record) { allow(record) {
record.set("action_name", "do_nothing"); record.set("action_name", "do_nothing");
record.save(); record.save();
} },
@action block(record) {
block(record) { record.set("action_name", "block");
record.set("action_name", "block"); record.save();
record.save(); },
}
@action cancel(record) {
cancel(record) { const savedIpAddress = this.savedIpAddress;
const savedIpAddress = this.savedIpAddress; if (savedIpAddress && record.get("editing")) {
if (savedIpAddress && record.get("editing")) { record.set("ip_address", savedIpAddress);
record.set("ip_address", savedIpAddress); }
} record.set("editing", false);
record.set("editing", false); },
}
@action save(record) {
save(record) { const wasEditing = record.get("editing");
const wasEditing = record.get("editing"); record.set("editing", false);
record.set("editing", false); record
record .save()
.save() .then(() => this.set("savedIpAddress", null))
.then(() => this.set("savedIpAddress", null)) .catch((e) => {
.catch((e) => { if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
if (wasEditing) {
record.set("editing", true);
}
});
}
@action
destroyRecord(record) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.logs.screened_ips.delete_confirm", {
ip_address: record.get("ip_address"),
}),
didConfirm: () => {
return record
.destroy()
.then((deleted) => {
if (deleted) {
this.model.removeObject(record);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
})
.catch((e) => {
this.dialog.alert( this.dialog.alert(
I18n.t("generic_error_with_reason", { I18n.t("generic_error_with_reason", {
error: `http: ${e.status} - ${e.body}`, error: e.jqXHR.responseJSON.errors.join(". "),
}) })
); );
}); } else {
}, this.dialog.alert(I18n.t("generic_error"));
}); }
} if (wasEditing) {
record.set("editing", true);
}
});
},
@action destroy(record) {
recordAdded(arg) { return this.dialog.yesNoConfirm({
this.model.unshiftObject(arg); message: I18n.t("admin.logs.screened_ips.delete_confirm", {
} ip_address: record.get("ip_address"),
}),
didConfirm: () => {
return record
.destroy()
.then((deleted) => {
if (deleted) {
this.model.removeObject(record);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
})
.catch((e) => {
this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: `http: ${e.status} - ${e.body}`,
})
);
});
},
});
},
@action recordAdded(arg) {
exportScreenedIpList() { this.model.unshiftObject(arg);
exportEntity("screened_ip").then(outputExportResult); },
}
} exportScreenedIpList() {
exportEntity("screened_ip").then(outputExportResult);
},
},
});

View File

@ -1,11 +1,10 @@
import { action } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ScreenedUrl from "admin/models/screened-url"; import ScreenedUrl from "admin/models/screened-url";
import { exportEntity } from "discourse/lib/export-csv"; import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result"; import { outputExportResult } from "discourse/lib/export-result";
export default class AdminLogsScreenedUrlsController extends Controller { export default Controller.extend({
loading = false; loading: false,
show() { show() {
this.set("loading", true); this.set("loading", true);
@ -13,10 +12,11 @@ export default class AdminLogsScreenedUrlsController extends Controller {
this.set("model", result); this.set("model", result);
this.set("loading", false); this.set("loading", false);
}); });
} },
@action actions: {
exportScreenedUrlList() { exportScreenedUrlList() {
exportEntity("screened_url").then(outputExportResult); exportEntity("screened_url").then(outputExportResult);
} },
} },
});

View File

@ -7,21 +7,22 @@ import { outputExportResult } from "discourse/lib/export-result";
import { scheduleOnce } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
export default class AdminLogsStaffActionLogsController extends Controller { export default Controller.extend({
queryParams = ["filters"]; queryParams: ["filters"],
model = null;
filters = null; model: null,
userHistoryActions = null; filters: null,
userHistoryActions: null,
@discourseComputed("filters.action_name") @discourseComputed("filters.action_name")
actionFilter(name) { actionFilter(name) {
return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null; return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null;
} },
@discourseComputed("filters") @discourseComputed("filters")
filtersExists(filters) { filtersExists(filters) {
return filters && Object.keys(filters).length > 0; return filters && Object.keys(filters).length > 0;
} },
_refresh() { _refresh() {
this.store.findAll("staff-action-log", this.filters).then((result) => { this.store.findAll("staff-action-log", this.filters).then((result) => {
@ -43,11 +44,11 @@ export default class AdminLogsStaffActionLogsController extends Controller {
); );
} }
}); });
} },
scheduleRefresh() { scheduleRefresh() {
scheduleOnce("afterRender", this, this._refresh); scheduleOnce("afterRender", this, this._refresh);
} },
resetFilters() { resetFilters() {
this.setProperties({ this.setProperties({
@ -55,7 +56,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
filters: EmberObject.create(), filters: EmberObject.create(),
}); });
this.scheduleRefresh(); this.scheduleRefresh();
} },
changeFilters(props) { changeFilters(props) {
this.set("model", EmberObject.create({ loadingMore: true })); this.set("model", EmberObject.create({ loadingMore: true }));
@ -75,7 +76,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
this.send("onFiltersChange", this.filters); this.send("onFiltersChange", this.filters);
this.scheduleRefresh(); this.scheduleRefresh();
} },
@action @action
filterActionIdChanged(filterActionId) { filterActionIdChanged(filterActionId) {
@ -86,7 +87,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
.action_id, .action_id,
}); });
} }
} },
@action @action
clearFilter(key, event) { clearFilter(key, event) {
@ -101,14 +102,14 @@ export default class AdminLogsStaffActionLogsController extends Controller {
} else { } else {
this.changeFilters({ [key]: null }); this.changeFilters({ [key]: null });
} }
} },
@action @action
clearAllFilters(event) { clearAllFilters(event) {
event?.preventDefault(); event?.preventDefault();
this.set("filterActionId", null); this.set("filterActionId", null);
this.resetFilters(); this.resetFilters();
} },
@action @action
filterByAction(logItem, event) { filterByAction(logItem, event) {
@ -118,35 +119,35 @@ export default class AdminLogsStaffActionLogsController extends Controller {
action_id: logItem.get("action"), action_id: logItem.get("action"),
custom_type: logItem.get("custom_type"), custom_type: logItem.get("custom_type"),
}); });
} },
@action @action
filterByStaffUser(acting_user, event) { filterByStaffUser(acting_user, event) {
event?.preventDefault(); event?.preventDefault();
this.changeFilters({ acting_user: acting_user.username }); this.changeFilters({ acting_user: acting_user.username });
} },
@action @action
filterByTargetUser(target_user, event) { filterByTargetUser(target_user, event) {
event?.preventDefault(); event?.preventDefault();
this.changeFilters({ target_user: target_user.username }); this.changeFilters({ target_user: target_user.username });
} },
@action @action
filterBySubject(subject, event) { filterBySubject(subject, event) {
event?.preventDefault(); event?.preventDefault();
this.changeFilters({ subject }); this.changeFilters({ subject });
} },
@action @action
exportStaffActionLogs() { exportStaffActionLogs() {
exportEntity("staff_action").then(outputExportResult); exportEntity("staff_action").then(outputExportResult);
} },
@action @action
loadMore() { loadMore() {
this.model.loadMore(); this.model.loadMore();
} },
@action @action
showDetailsModal(model, event) { showDetailsModal(model, event) {
@ -156,7 +157,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
admin: true, admin: true,
modalClass: "log-details-modal", modalClass: "log-details-modal",
}); });
} },
@action @action
showCustomDetailsModal(model, event) { showCustomDetailsModal(model, event) {
@ -167,5 +168,5 @@ export default class AdminLogsStaffActionLogsController extends Controller {
modalClass: "history-modal", modalClass: "history-modal",
}); });
modal.loadDiff(); modal.loadDiff();
} },
} });

View File

@ -1,63 +1,59 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import Permalink from "admin/models/permalink"; import Permalink from "admin/models/permalink";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
import { clipboardCopy } from "discourse/lib/utilities"; import { clipboardCopy } from "discourse/lib/utilities";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
export default class AdminPermalinksController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
loading: false,
loading = false; filter: null,
filter = null; showSearch: or("model.length", "filter"),
@or("model.length", "filter") showSearch;
_debouncedShow() { _debouncedShow() {
Permalink.findAll(this.filter).then((result) => { Permalink.findAll(this.filter).then((result) => {
this.set("model", result); this.set("model", result);
this.set("loading", false); this.set("loading", false);
}); });
} },
@observes("filter") @observes("filter")
show() { show() {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY); discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
} },
@action actions: {
recordAdded(arg) { recordAdded(arg) {
this.model.unshiftObject(arg); this.model.unshiftObject(arg);
} },
@action copyUrl(pl) {
copyUrl(pl) { let linkElement = document.querySelector(`#admin-permalink-${pl.id}`);
let linkElement = document.querySelector(`#admin-permalink-${pl.id}`); clipboardCopy(linkElement.textContent);
clipboardCopy(linkElement.textContent); },
}
@action destroy(record) {
destroyRecord(record) { return this.dialog.yesNoConfirm({
return this.dialog.yesNoConfirm({ message: I18n.t("admin.permalink.delete_confirm"),
message: I18n.t("admin.permalink.delete_confirm"), didConfirm: () => {
didConfirm: () => { return record.destroy().then(
return record.destroy().then( (deleted) => {
(deleted) => { if (deleted) {
if (deleted) { this.model.removeObject(record);
this.model.removeObject(record); } else {
} else { this.dialog.alert(I18n.t("generic_error"));
}
},
function () {
this.dialog.alert(I18n.t("generic_error")); this.dialog.alert(I18n.t("generic_error"));
} }
}, );
function () { },
this.dialog.alert(I18n.t("generic_error")); });
} },
); },
}, });
});
}
}

View File

@ -1,18 +1,18 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { inject as service } from "@ember/service";
export default class AdminPluginsController extends Controller { export default Controller.extend({
@service router; router: service(),
get adminRoutes() { get adminRoutes() {
return this.allAdminRoutes.filter((r) => this.routeExists(r.full_location)); return this.allAdminRoutes.filter((r) => this.routeExists(r.full_location));
} },
get brokenAdminRoutes() { get brokenAdminRoutes() {
return this.allAdminRoutes.filter( return this.allAdminRoutes.filter(
(r) => !this.routeExists(r.full_location) (r) => !this.routeExists(r.full_location)
); );
} },
get allAdminRoutes() { get allAdminRoutes() {
return this.model return this.model
@ -21,7 +21,7 @@ export default class AdminPluginsController extends Controller {
return p.admin_route; return p.admin_route;
}) })
.filter(Boolean); .filter(Boolean);
} },
routeExists(routeName) { routeExists(routeName) {
try { try {
@ -30,5 +30,5 @@ export default class AdminPluginsController extends Controller {
} catch (e) { } catch (e) {
return false; return false;
} }
} },
} });

View File

@ -1,12 +1,12 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default class AdminReportsShowController extends Controller { export default Controller.extend({
queryParams = ["start_date", "end_date", "filters", "chart_grouping", "mode"]; queryParams: ["start_date", "end_date", "filters", "chart_grouping", "mode"],
start_date = null; start_date: null,
end_date = null; end_date: null,
filters = null; filters: null,
chart_grouping = null; chart_grouping: null,
@discourseComputed("model.type") @discourseComputed("model.type")
reportOptions(type) { reportOptions(type) {
@ -19,5 +19,5 @@ export default class AdminReportsShowController extends Controller {
options.chartGrouping = this.chart_grouping; options.chartGrouping = this.chart_grouping;
return options; return options;
} },
} });

View File

@ -2,19 +2,24 @@ import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
export const DEFAULT_PERIOD = "yearly"; export const DEFAULT_PERIOD = "yearly";
export default class AdminSearchLogsIndexController extends Controller { export default Controller.extend({
loading = false; loading: false,
period = DEFAULT_PERIOD; period: DEFAULT_PERIOD,
searchType = "all"; searchType: "all",
searchTypeOptions = [
{ init() {
id: "all", this._super(...arguments);
name: I18n.t("admin.logs.search_logs.types.all_search_types"),
}, this.searchTypeOptions = [
{ id: "header", name: I18n.t("admin.logs.search_logs.types.header") }, {
{ id: "all",
id: "full_page", name: I18n.t("admin.logs.search_logs.types.all_search_types"),
name: I18n.t("admin.logs.search_logs.types.full_page"), },
}, { id: "header", name: I18n.t("admin.logs.search_logs.types.header") },
]; {
} id: "full_page",
name: I18n.t("admin.logs.search_logs.types.full_page"),
},
];
},
});

View File

@ -2,24 +2,29 @@ import Controller from "@ember/controller";
import { DEFAULT_PERIOD } from "admin/controllers/admin-search-logs-index"; import { DEFAULT_PERIOD } from "admin/controllers/admin-search-logs-index";
import I18n from "I18n"; import I18n from "I18n";
export default class AdminSearchLogsTermController extends Controller { export default Controller.extend({
loading = false; loading: false,
term = null; term: null,
period = DEFAULT_PERIOD; period: DEFAULT_PERIOD,
searchType = "all"; searchType: "all",
searchTypeOptions = [
{ init() {
id: "all", this._super(...arguments);
name: I18n.t("admin.logs.search_logs.types.all_search_types"),
}, this.searchTypeOptions = [
{ id: "header", name: I18n.t("admin.logs.search_logs.types.header") }, {
{ id: "all",
id: "full_page", name: I18n.t("admin.logs.search_logs.types.all_search_types"),
name: I18n.t("admin.logs.search_logs.types.full_page"), },
}, { id: "header", name: I18n.t("admin.logs.search_logs.types.header") },
{ {
id: "click_through_only", id: "full_page",
name: I18n.t("admin.logs.search_logs.types.click_through_only"), name: I18n.t("admin.logs.search_logs.types.full_page"),
}, },
]; {
} id: "click_through_only",
name: I18n.t("admin.logs.search_logs.types.click_through_only"),
},
];
},
});

View File

@ -1,18 +1,17 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default class AdminSiteSettingsCategoryController extends Controller { export default Controller.extend({
@controller adminSiteSettings; adminSiteSettings: controller(),
categoryNameKey: null,
categoryNameKey = null;
@discourseComputed("adminSiteSettings.visibleSiteSettings", "categoryNameKey") @discourseComputed("adminSiteSettings.visibleSiteSettings", "categoryNameKey")
category(categories, nameKey) { category(categories, nameKey) {
return (categories || []).findBy("nameKey", nameKey); return (categories || []).findBy("nameKey", nameKey);
} },
@discourseComputed("category") @discourseComputed("category")
filteredContent(category) { filteredContent(category) {
return category ? category.siteSettings : []; return category ? category.siteSettings : [];
} },
} });

View File

@ -1,19 +1,16 @@
import { alias } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import { alias } from "@ember/object/computed";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { debounce } from "discourse-common/utils/decorators"; import { debounce, observes } from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import { action } from "@ember/object"; import { action } from "@ember/object";
export default class AdminSiteSettingsController extends Controller { export default Controller.extend({
filter = null; filter: null,
allSiteSettings: alias("model"),
@alias("model") allSiteSettings; visibleSiteSettings: null,
onlyOverridden: false,
visibleSiteSettings = null;
onlyOverridden = false;
filterContentNow(category) { filterContentNow(category) {
// If we have no content, don't bother filtering anything // If we have no content, don't bother filtering anything
@ -112,13 +109,9 @@ export default class AdminSiteSettingsController extends Controller {
"adminSiteSettingsCategory", "adminSiteSettingsCategory",
category || "all_results" category || "all_results"
); );
} },
@observes("filter", "onlyOverridden", "model") @observes("filter", "onlyOverridden", "model")
optsChanged() {
this.filterContent();
}
@debounce(INPUT_DELAY) @debounce(INPUT_DELAY)
filterContent() { filterContent() {
if (this._skipBounce) { if (this._skipBounce) {
@ -126,12 +119,12 @@ export default class AdminSiteSettingsController extends Controller {
} else { } else {
this.filterContentNow(this.categoryNameKey); this.filterContentNow(this.categoryNameKey);
} }
} },
@action @action
clearFilter() { clearFilter() {
this.setProperties({ filter: "", onlyOverridden: false }); this.setProperties({ filter: "", onlyOverridden: false });
} },
@action @action
toggleMenu() { toggleMenu() {
@ -139,5 +132,5 @@ export default class AdminSiteSettingsController extends Controller {
["mobile-closed", "mobile-open"].forEach((state) => { ["mobile-closed", "mobile-open"].forEach((state) => {
adminDetail.classList.toggle(state); adminDetail.classList.toggle(state);
}); });
} },
} });

View File

@ -1,23 +1,23 @@
import { action } from "@ember/object";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
let lastSearch; let lastSearch;
export default class AdminSiteTextIndexController extends Controller { export default Controller.extend({
searching = false; searching: false,
siteTexts = null; siteTexts: null,
preferred = false; preferred: false,
queryParams = ["q", "overridden", "locale"]; queryParams: ["q", "overridden", "locale"],
locale = null; locale: null,
q = null;
overridden = false; q: null,
overridden: false,
init() { init() {
super.init(...arguments); this._super(...arguments);
this.set("locale", this.siteSettings.default_locale); this.set("locale", this.siteSettings.default_locale);
} },
_performSearch() { _performSearch() {
this.store this.store
@ -26,12 +26,12 @@ export default class AdminSiteTextIndexController extends Controller {
this.set("siteTexts", results); this.set("siteTexts", results);
}) })
.finally(() => this.set("searching", false)); .finally(() => this.set("searching", false));
} },
@discourseComputed() @discourseComputed()
availableLocales() { availableLocales() {
return JSON.parse(this.siteSettings.available_locales); return JSON.parse(this.siteSettings.available_locales);
} },
@discourseComputed("locale") @discourseComputed("locale")
fallbackLocaleFullName() { fallbackLocaleFullName() {
@ -40,41 +40,39 @@ export default class AdminSiteTextIndexController extends Controller {
return l.value === this.siteTexts.extras.fallback_locale; return l.value === this.siteTexts.extras.fallback_locale;
}).name; }).name;
} }
} },
@action actions: {
edit(siteText) { edit(siteText) {
this.transitionToRoute("adminSiteText.edit", siteText.get("id"), { this.transitionToRoute("adminSiteText.edit", siteText.get("id"), {
queryParams: { queryParams: {
locale: this.locale, locale: this.locale,
}, },
}); });
} },
@action toggleOverridden() {
toggleOverridden() { this.toggleProperty("overridden");
this.toggleProperty("overridden");
this.set("searching", true);
discourseDebounce(this, this._performSearch, 400);
}
@action
search() {
const q = this.q;
if (q !== lastSearch) {
this.set("searching", true); this.set("searching", true);
discourseDebounce(this, this._performSearch, 400); discourseDebounce(this, this._performSearch, 400);
lastSearch = q; },
}
}
@action search() {
updateLocale(value) { const q = this.q;
this.setProperties({ if (q !== lastSearch) {
searching: true, this.set("searching", true);
locale: value, discourseDebounce(this, this._performSearch, 400);
}); lastSearch = q;
}
},
discourseDebounce(this, this._performSearch, 400); updateLocale(value) {
} this.setProperties({
} searching: true,
locale: value,
});
discourseDebounce(this, this._performSearch, 400);
},
},
});

View File

@ -1,25 +1,25 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { alias, sort } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { alias, sort } from "@ember/object/computed";
import GrantBadgeController from "discourse/mixins/grant-badge-controller"; import GrantBadgeController from "discourse/mixins/grant-badge-controller";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminUserBadgesController extends Controller.extend( export default Controller.extend(GrantBadgeController, {
GrantBadgeController adminUser: controller(),
) { dialog: service(),
@service dialog; user: alias("adminUser.model"),
@controller adminUser; userBadges: alias("model"),
allBadges: alias("badges"),
sortedBadges: sort("model", "badgeSortOrder"),
@alias("adminUser.model") user; init() {
@alias("model") userBadges; this._super(...arguments);
@alias("badges") allBadges;
@sort("model", "badgeSortOrder") sortedBadges;
badgeSortOrder = ["granted_at:desc"]; this.badgeSortOrder = ["granted_at:desc"];
},
@discourseComputed("model", "model.[]", "model.expandedBadges.[]") @discourseComputed("model", "model.[]", "model.expandedBadges.[]")
groupedBadges() { groupedBadges() {
@ -59,47 +59,46 @@ export default class AdminUserBadgesController extends Controller.extend(
}); });
return expanded.sortBy("granted_at").reverse(); return expanded.sortBy("granted_at").reverse();
} },
@action actions: {
expandGroup(userBadge) { expandGroup(userBadge) {
const model = this.model; const model = this.model;
model.set("expandedBadges", model.get("expandedBadges") || []); model.set("expandedBadges", model.get("expandedBadges") || []);
model.get("expandedBadges").pushObject(userBadge.badge.id); model.get("expandedBadges").pushObject(userBadge.badge.id);
} },
@action grantBadge() {
grantBadge() { this.grantBadge(
this.grantBadge( this.selectedBadgeId,
this.selectedBadgeId, this.get("user.username"),
this.get("user.username"), this.badgeReason
this.badgeReason ).then(
).then( () => {
() => { this.set("badgeReason", "");
this.set("badgeReason", ""); next(() => {
next(() => { // Update the selected badge ID after the combobox has re-rendered.
// Update the selected badge ID after the combobox has re-rendered. const newSelectedBadge = this.grantableBadges[0];
const newSelectedBadge = this.grantableBadges[0]; if (newSelectedBadge) {
if (newSelectedBadge) { this.set("selectedBadgeId", newSelectedBadge.get("id"));
this.set("selectedBadgeId", newSelectedBadge.get("id")); }
} });
}); },
}, function (error) {
function (error) { popupAjaxError(error);
popupAjaxError(error); }
} );
); },
}
@action revokeBadge(userBadge) {
revokeBadge(userBadge) { return this.dialog.yesNoConfirm({
return this.dialog.yesNoConfirm({ message: I18n.t("admin.badges.revoke_confirm"),
message: I18n.t("admin.badges.revoke_confirm"), didConfirm: () => {
didConfirm: () => { return userBadge.revoke().then(() => {
return userBadge.revoke().then(() => { this.model.removeObject(userBadge);
this.model.removeObject(userBadge); });
}); },
}, });
}); },
} },
} });

View File

@ -1,74 +1,73 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { gte, sort } from "@ember/object/computed"; import { gte, sort } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
const MAX_FIELDS = 30; const MAX_FIELDS = 30;
export default class AdminUserFieldsController extends Controller { export default Controller.extend({
@service dialog; dialog: service(),
fieldTypes: null,
createDisabled: gte("model.length", MAX_FIELDS),
sortedFields: sort("model", "fieldSortOrder"),
fieldTypes = null; init() {
this._super(...arguments);
@gte("model.length", MAX_FIELDS) createDisabled; this.fieldSortOrder = ["position"];
@sort("model", "fieldSortOrder") sortedFields; },
fieldSortOrder = ["position"]; actions: {
createField() {
@action const f = this.store.createRecord("user-field", {
createField() { field_type: "text",
const f = this.store.createRecord("user-field", { position: MAX_FIELDS,
field_type: "text",
position: MAX_FIELDS,
});
this.model.pushObject(f);
}
@action
moveUp(f) {
const idx = this.sortedFields.indexOf(f);
if (idx) {
const prev = this.sortedFields.objectAt(idx - 1);
const prevPos = prev.get("position");
prev.update({ position: f.get("position") });
f.update({ position: prevPos });
}
}
@action
moveDown(f) {
const idx = this.sortedFields.indexOf(f);
if (idx > -1) {
const next = this.sortedFields.objectAt(idx + 1);
const nextPos = next.get("position");
next.update({ position: f.get("position") });
f.update({ position: nextPos });
}
}
@action
destroyField(f) {
const model = this.model;
// Only confirm if we already been saved
if (f.get("id")) {
this.dialog.yesNoConfirm({
message: I18n.t("admin.user_fields.delete_confirm"),
didConfirm: () => {
return f
.destroyRecord()
.then(function () {
model.removeObject(f);
})
.catch(popupAjaxError);
},
}); });
} else { this.model.pushObject(f);
model.removeObject(f); },
}
} moveUp(f) {
} const idx = this.sortedFields.indexOf(f);
if (idx) {
const prev = this.sortedFields.objectAt(idx - 1);
const prevPos = prev.get("position");
prev.update({ position: f.get("position") });
f.update({ position: prevPos });
}
},
moveDown(f) {
const idx = this.sortedFields.indexOf(f);
if (idx > -1) {
const next = this.sortedFields.objectAt(idx + 1);
const nextPos = next.get("position");
next.update({ position: f.get("position") });
f.update({ position: nextPos });
}
},
destroy(f) {
const model = this.model;
// Only confirm if we already been saved
if (f.get("id")) {
this.dialog.yesNoConfirm({
message: I18n.t("admin.user_fields.delete_confirm"),
didConfirm: () => {
return f
.destroyRecord()
.then(function () {
model.removeObject(f);
})
.catch(popupAjaxError);
},
});
} else {
model.removeObject(f);
}
},
},
});

View File

@ -1,2 +1,2 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
export default class AdminUserController extends Controller {} export default Controller.extend();

View File

@ -1,6 +1,4 @@
import { action } from "@ember/object"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import AdminUser from "admin/models/admin-user"; import AdminUser from "admin/models/admin-user";
import CanCheckEmails from "discourse/mixins/can-check-emails"; import CanCheckEmails from "discourse/mixins/can-check-emails";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
@ -9,55 +7,41 @@ import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { i18n } from "discourse/lib/computed"; import { i18n } from "discourse/lib/computed";
export default class AdminUsersListShowController extends Controller.extend( export default Controller.extend(CanCheckEmails, {
CanCheckEmails model: null,
) { query: null,
model = null; order: null,
query = null; asc: null,
order = null; showEmails: false,
asc = null; refreshing: false,
showEmails = false; listFilter: null,
refreshing = false; selectAll: false,
listFilter = null; searchHint: i18n("search_hint"),
selectAll = false;
@i18n("search_hint") searchHint; init() {
this._super(...arguments);
_page = 1; this._page = 1;
_results = []; this._results = [];
_canLoadMore = true; this._canLoadMore = true;
},
@discourseComputed("query") @discourseComputed("query")
title(query) { title(query) {
return I18n.t("admin.users.titles." + query); return I18n.t("admin.users.titles." + query);
} },
@discourseComputed("showEmails")
columnCount(showEmails) {
let colCount = 7; // note that the first column is hardcoded in the template
if (showEmails) {
colCount += 1;
}
if (this.siteSettings.must_approve_users) {
colCount += 1;
}
return colCount;
}
@observes("listFilter") @observes("listFilter")
_filterUsers() { _filterUsers() {
discourseDebounce(this, this.resetFilters, INPUT_DELAY); discourseDebounce(this, this.resetFilters, INPUT_DELAY);
} },
resetFilters() { resetFilters() {
this._page = 1; this._page = 1;
this._results = []; this._results = [];
this._canLoadMore = true; this._canLoadMore = true;
this._refreshUsers(); this._refreshUsers();
} },
_refreshUsers() { _refreshUsers() {
if (!this._canLoadMore) { if (!this._canLoadMore) {
@ -85,17 +69,17 @@ export default class AdminUsersListShowController extends Controller.extend(
.finally(() => { .finally(() => {
this.set("refreshing", false); this.set("refreshing", false);
}); });
} },
@action actions: {
loadMore() { loadMore() {
this._page += 1; this._page += 1;
this._refreshUsers(); this._refreshUsers();
} },
@action toggleEmailVisibility() {
toggleEmailVisibility() { this.toggleProperty("showEmails");
this.toggleProperty("showEmails"); this.resetFilters();
this.resetFilters(); },
} },
} });

View File

@ -1,35 +1,32 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import WatchedWord from "admin/models/watched-word"; import WatchedWord from "admin/models/watched-word";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed"; import { fmt } from "discourse/lib/computed";
import { or } from "@ember/object/computed";
import { schedule } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service";
export default class AdminWatchedWordsActionController extends Controller { export default Controller.extend({
@service dialog; adminWatchedWords: controller(),
@controller adminWatchedWords; actionNameKey: null,
dialog: service(),
actionNameKey = null; downloadLink: fmt(
"actionNameKey",
@fmt("actionNameKey", "/admin/customize/watched_words/action/%@/download") "/admin/customize/watched_words/action/%@/download"
downloadLink; ),
showWordsList: or("adminWatchedWords.showWords", "adminWatchedWords.filter"),
@or("adminWatchedWords.showWords", "adminWatchedWords.filter")
showWordsList;
findAction(actionName) { findAction(actionName) {
return (this.adminWatchedWords.model || []).findBy("nameKey", actionName); return (this.adminWatchedWords.model || []).findBy("nameKey", actionName);
} },
@discourseComputed("actionNameKey", "adminWatchedWords.model") @discourseComputed("actionNameKey", "adminWatchedWords.model")
currentAction(actionName) { currentAction(actionName) {
return this.findAction(actionName); return this.findAction(actionName);
} },
@discourseComputed("currentAction.words.[]") @discourseComputed("currentAction.words.[]")
regexpError(words) { regexpError(words) {
@ -40,81 +37,78 @@ export default class AdminWatchedWordsActionController extends Controller {
return I18n.t("admin.watched_words.invalid_regex", { word }); return I18n.t("admin.watched_words.invalid_regex", { word });
} }
} }
} },
@discourseComputed("actionNameKey") @discourseComputed("actionNameKey")
actionDescription(actionNameKey) { actionDescription(actionNameKey) {
return I18n.t("admin.watched_words.action_descriptions." + actionNameKey); return I18n.t("admin.watched_words.action_descriptions." + actionNameKey);
} },
@action actions: {
recordAdded(arg) { recordAdded(arg) {
const foundAction = this.findAction(this.actionNameKey); const action = this.findAction(this.actionNameKey);
if (!foundAction) { if (!action) {
return; return;
} }
foundAction.words.unshiftObject(arg); action.words.unshiftObject(arg);
schedule("afterRender", () => { schedule("afterRender", () => {
// remove from other actions lists // remove from other actions lists
let match = null; let match = null;
this.adminWatchedWords.model.forEach((otherAction) => { this.adminWatchedWords.model.forEach((otherAction) => {
if (match) {
return;
}
if (otherAction.nameKey !== this.actionNameKey) {
match = otherAction.words.findBy("id", arg.id);
if (match) { if (match) {
otherAction.words.removeObject(match); return;
} }
}
});
});
}
@action if (otherAction.nameKey !== this.actionNameKey) {
recordRemoved(arg) { match = otherAction.words.findBy("id", arg.id);
if (this.currentAction) { if (match) {
this.currentAction.words.removeObject(arg); otherAction.words.removeObject(match);
} }
}
@action
uploadComplete() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
});
}
@action
test() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
showModal("admin-watched-word-test", {
admin: true,
model: this.currentAction,
});
});
}
@action
clearAll() {
const actionKey = this.actionNameKey;
this.dialog.yesNoConfirm({
message: I18n.t("admin.watched_words.clear_all_confirm", {
action: I18n.t("admin.watched_words.actions." + actionKey),
}),
didConfirm: () => {
ajax(`/admin/customize/watched_words/action/${actionKey}.json`, {
type: "DELETE",
}).then(() => {
const foundAction = this.findAction(actionKey);
if (foundAction) {
foundAction.set("words", []);
} }
}); });
}, });
}); },
}
} recordRemoved(arg) {
if (this.currentAction) {
this.currentAction.words.removeObject(arg);
}
},
uploadComplete() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
});
},
test() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
showModal("admin-watched-word-test", {
admin: true,
model: this.currentAction,
});
});
},
clearAll() {
const actionKey = this.actionNameKey;
this.dialog.yesNoConfirm({
message: I18n.t("admin.watched_words.clear_all_confirm", {
action: I18n.t("admin.watched_words.actions." + actionKey),
}),
didConfirm: () => {
ajax(`/admin/customize/watched_words/action/${actionKey}.json`, {
type: "DELETE",
}).then(() => {
const action = this.findAction(actionKey);
if (action) {
action.set("words", []);
}
});
},
});
},
},
});

View File

@ -3,11 +3,11 @@ import EmberObject, { action } from "@ember/object";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { observes } from "@ember-decorators/object"; import { observes } from "discourse-common/utils/decorators";
export default class AdminWatchedWordsController extends Controller { export default Controller.extend({
filter = null; filter: null,
showWords = false; showWords: false,
_filterContent() { _filterContent() {
if (isEmpty(this.allWatchedWords)) { if (isEmpty(this.allWatchedWords)) {
@ -36,17 +36,17 @@ export default class AdminWatchedWordsController extends Controller {
); );
}); });
this.set("model", model); this.set("model", model);
} },
@observes("filter") @observes("filter")
filterContent() { filterContent() {
discourseDebounce(this, this._filterContent, INPUT_DELAY); discourseDebounce(this, this._filterContent, INPUT_DELAY);
} },
@action @action
clearFilter() { clearFilter() {
this.set("filter", ""); this.set("filter", "");
} },
@action @action
toggleMenu() { toggleMenu() {
@ -54,5 +54,5 @@ export default class AdminWatchedWordsController extends Controller {
["mobile-closed", "mobile-open"].forEach((state) => { ["mobile-closed", "mobile-open"].forEach((state) => {
adminDetail.classList.toggle(state); adminDetail.classList.toggle(state);
}); });
} },
} });

View File

@ -1,24 +1,23 @@
import { inject as service } from "@ember/service";
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import EmberObject, { action } from "@ember/object"; import EmberObject, { action } from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminWebHooksEditController extends Controller { export default Controller.extend({
@service dialog; adminWebHooks: controller(),
@controller adminWebHooks; dialog: service(),
eventTypes: alias("adminWebHooks.eventTypes"),
@alias("adminWebHooks.eventTypes") eventTypes; defaultEventTypes: alias("adminWebHooks.defaultEventTypes"),
@alias("adminWebHooks.defaultEventTypes") defaultEventTypes; contentTypes: alias("adminWebHooks.contentTypes"),
@alias("adminWebHooks.contentTypes") contentTypes;
@discourseComputed @discourseComputed
showTagsFilter() { showTagsFilter() {
return this.siteSettings.tagging_enabled; return this.siteSettings.tagging_enabled;
} },
@discourseComputed("model.isSaving", "saved", "saveButtonDisabled") @discourseComputed("model.isSaving", "saved", "saveButtonDisabled")
savingStatus(isSaving, saved, saveButtonDisabled) { savingStatus(isSaving, saved, saveButtonDisabled) {
@ -30,14 +29,14 @@ export default class AdminWebHooksEditController extends Controller {
// Use side effect of validation to clear saved text // Use side effect of validation to clear saved text
this.set("saved", false); this.set("saved", false);
return ""; return "";
} },
@discourseComputed("model.isNew") @discourseComputed("model.isNew")
saveButtonText(isNew) { saveButtonText(isNew) {
return isNew return isNew
? I18n.t("admin.web_hooks.create") ? I18n.t("admin.web_hooks.create")
: I18n.t("admin.web_hooks.save"); : I18n.t("admin.web_hooks.save");
} },
@discourseComputed("model.secret") @discourseComputed("model.secret")
secretValidation(secret) { secretValidation(secret) {
@ -56,7 +55,7 @@ export default class AdminWebHooksEditController extends Controller {
}); });
} }
} }
} },
@discourseComputed("model.wildcard_web_hook", "model.web_hook_event_types.[]") @discourseComputed("model.wildcard_web_hook", "model.web_hook_event_types.[]")
eventTypeValidation(isWildcard, eventTypes) { eventTypeValidation(isWildcard, eventTypes) {
@ -66,7 +65,7 @@ export default class AdminWebHooksEditController extends Controller {
reason: I18n.t("admin.web_hooks.event_type_missing"), reason: I18n.t("admin.web_hooks.event_type_missing"),
}); });
} }
} },
@discourseComputed( @discourseComputed(
"model.isSaving", "model.isSaving",
@ -83,7 +82,7 @@ export default class AdminWebHooksEditController extends Controller {
return isSaving return isSaving
? false ? false
: secretValidation || eventTypeValidation || isEmpty(payloadUrl); : secretValidation || eventTypeValidation || isEmpty(payloadUrl);
} },
@action @action
async save() { async save() {
@ -98,5 +97,5 @@ export default class AdminWebHooksEditController extends Controller {
} catch (e) { } catch (e) {
popupAjaxError(e); popupAjaxError(e);
} }
} },
} });

View File

@ -1,19 +1,18 @@
import { inject as service } from "@ember/service";
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
export default class AdminWebHooksIndexController extends Controller { export default Controller.extend({
@service dialog; adminWebHooks: controller(),
@controller adminWebHooks; dialog: service(),
contentTypes: alias("adminWebHooks.contentTypes"),
@alias("adminWebHooks.contentTypes") contentTypes; defaultEventTypes: alias("adminWebHooks.defaultEventTypes"),
@alias("adminWebHooks.defaultEventTypes") defaultEventTypes; deliveryStatuses: alias("adminWebHooks.deliveryStatuses"),
@alias("adminWebHooks.deliveryStatuses") deliveryStatuses; eventTypes: alias("adminWebHooks.eventTypes"),
@alias("adminWebHooks.eventTypes") eventTypes; model: alias("adminWebHooks.model"),
@alias("adminWebHooks.model") model;
@action @action
destroy(webhook) { destroy(webhook) {
@ -28,10 +27,10 @@ export default class AdminWebHooksIndexController extends Controller {
} }
}, },
}); });
} },
@action @action
loadMore() { loadMore() {
this.model.loadMore(); this.model.loadMore();
} },
} });

View File

@ -1,18 +1,18 @@
import { inject as service } from "@ember/service";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { action } from "@ember/object"; import { action } from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminWebHooksShowController extends Controller { export default Controller.extend({
@service dialog; adminWebHooks: controller(),
@service router; dialog: service(),
@controller adminWebHooks; router: service(),
@action @action
edit() { edit() {
return this.router.transitionTo("adminWebHooks.edit", this.model); return this.router.transitionTo("adminWebHooks.edit", this.model);
} },
@action @action
destroy() { destroy() {
@ -28,5 +28,5 @@ export default class AdminWebHooksShowController extends Controller {
} }
}, },
}); });
} },
} });

View File

@ -1,3 +1,3 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
export default class AdminWebHooksController extends Controller {} export default Controller.extend({});

View File

@ -1,20 +1,20 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminController extends Controller { export default Controller.extend({
@service router; router: service(),
@discourseComputed("siteSettings.enable_group_directory") @discourseComputed("siteSettings.enable_group_directory")
showGroups(enableGroupDirectory) { showGroups(enableGroupDirectory) {
return !enableGroupDirectory; return !enableGroupDirectory;
} },
@discourseComputed("siteSettings.enable_badges") @discourseComputed("siteSettings.enable_badges")
showBadges(enableBadges) { showBadges(enableBadges) {
return this.currentUser.get("admin") && enableBadges; return this.currentUser.get("admin") && enableBadges;
} },
@discourseComputed("router._router.currentPath") @discourseComputed("router._router.currentPath")
adminContentsClassName(currentPath) { adminContentsClassName(currentPath) {
@ -37,5 +37,5 @@ export default class AdminController extends Controller {
} }
return cssClasses; return cssClasses;
} },
} });

View File

@ -1,8 +1,6 @@
import { action } from "@ember/object";
import { and, not } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import { and, not } from "@ember/object/computed";
import { observes } from "@ember-decorators/object"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
@ -55,20 +53,18 @@ const SCSS_VARIABLE_NAMES = [
"love-low", "love-low",
]; ];
export default class AdminAddUploadController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminCustomizeThemesShow: controller(),
) {
@controller adminCustomizeThemesShow;
uploadUrl = "/admin/themes/upload_asset"; uploadUrl: "/admin/themes/upload_asset",
@and("nameValid", "fileSelected") enabled;
@not("enabled") disabled;
onShow() { onShow() {
this.set("name", null); this.set("name", null);
this.set("fileSelected", false); this.set("fileSelected", false);
} },
enabled: and("nameValid", "fileSelected"),
disabled: not("enabled"),
@discourseComputed("name", "adminCustomizeThemesShow.model.theme_fields") @discourseComputed("name", "adminCustomizeThemesShow.model.theme_fields")
errorMessage(name, themeFields) { errorMessage(name, themeFields) {
@ -93,54 +89,54 @@ export default class AdminAddUploadController extends Controller.extend(
} }
return null; return null;
} },
@discourseComputed("errorMessage") @discourseComputed("errorMessage")
nameValid(errorMessage) { nameValid(errorMessage) {
return null === errorMessage; return null === errorMessage;
} },
@observes("name") @observes("name")
uploadChanged() { uploadChanged() {
const file = $("#file-input")[0]; const file = $("#file-input")[0];
this.set("fileSelected", file && file.files[0]); this.set("fileSelected", file && file.files[0]);
} },
@action actions: {
updateName() { updateName() {
let name = this.name; let name = this.name;
if (isEmpty(name)) { if (isEmpty(name)) {
name = $("#file-input")[0].files[0].name; name = $("#file-input")[0].files[0].name;
this.set("name", name.split(".")[0]); this.set("name", name.split(".")[0]);
} }
this.uploadChanged(); this.uploadChanged();
} },
@action upload() {
upload() { const file = $("#file-input")[0].files[0];
const file = $("#file-input")[0].files[0];
const options = { const options = {
type: "POST", type: "POST",
processData: false, processData: false,
contentType: false, contentType: false,
data: new FormData(), data: new FormData(),
}; };
options.data.append("file", file); options.data.append("file", file);
ajax(this.uploadUrl, options) ajax(this.uploadUrl, options)
.then((result) => { .then((result) => {
const upload = { const upload = {
upload_id: result.upload_id, upload_id: result.upload_id,
name: this.name, name: this.name,
original_filename: file.name, original_filename: file.name,
}; };
this.adminCustomizeThemesShow.send("addUpload", upload); this.adminCustomizeThemesShow.send("addUpload", upload);
this.send("closeModal"); this.send("closeModal");
}) })
.catch((e) => { .catch((e) => {
popupAjaxError(e); popupAjaxError(e);
}); });
} },
} },
});

View File

@ -4,12 +4,39 @@ import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
export default class AdminBadgePreviewController extends Controller { export default Controller.extend({
@alias("model.sample") sample; sample: alias("model.sample"),
@alias("model.errors") errors; errors: alias("model.errors"),
@alias("model.grant_count") count; count: alias("model.grant_count"),
@map("model.sample", (grant) => { @discourseComputed("count", "sample.length")
countWarning(count, sampleLength) {
if (count <= 10) {
return sampleLength !== count;
} else {
return sampleLength !== 10;
}
},
@discourseComputed("model.query_plan")
hasQueryPlan(queryPlan) {
return !!queryPlan;
},
@discourseComputed("model.query_plan")
queryPlanHtml(queryPlan) {
let output = `<pre class="badge-query-plan">`;
queryPlan.forEach((linehash) => {
output += escapeExpression(linehash["QUERY PLAN"]);
output += "<br>";
});
output += "</pre>";
return output;
},
processedSample: map("model.sample", (grant) => {
let i18nKey = "admin.badges.preview.grant.with"; let i18nKey = "admin.badges.preview.grant.with";
const i18nParams = { username: escapeExpression(grant.username) }; const i18nParams = { username: escapeExpression(grant.username) };
@ -28,33 +55,5 @@ export default class AdminBadgePreviewController extends Controller {
} }
return I18n.t(i18nKey, i18nParams); return I18n.t(i18nKey, i18nParams);
}) }),
processedSample; });
@discourseComputed("count", "sample.length")
countWarning(count, sampleLength) {
if (count <= 10) {
return sampleLength !== count;
} else {
return sampleLength !== 10;
}
}
@discourseComputed("model.query_plan")
hasQueryPlan(queryPlan) {
return !!queryPlan;
}
@discourseComputed("model.query_plan")
queryPlanHtml(queryPlan) {
let output = `<pre class="badge-query-plan">`;
queryPlan.forEach((linehash) => {
output += escapeExpression(linehash["QUERY PLAN"]);
output += "<br>";
});
output += "</pre>";
return output;
}
}

View File

@ -1,29 +1,27 @@
import { action } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminColorSchemeSelectBaseController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminCustomizeColors: controller(),
) {
@controller adminCustomizeColors;
selectedBaseThemeId = null; selectedBaseThemeId: null,
init() { init() {
super.init(...arguments); this._super(...arguments);
const defaultScheme = this.get( const defaultScheme = this.get(
"adminCustomizeColors.baseColorSchemes.0.base_scheme_id" "adminCustomizeColors.baseColorSchemes.0.base_scheme_id"
); );
this.set("selectedBaseThemeId", defaultScheme); this.set("selectedBaseThemeId", defaultScheme);
} },
@action actions: {
selectBase() { selectBase() {
this.adminCustomizeColors.send( this.adminCustomizeColors.send(
"newColorSchemeWithBase", "newColorSchemeWithBase",
this.selectedBaseThemeId this.selectedBaseThemeId
); );
this.send("closeModal"); this.send("closeModal");
} },
} },
});

View File

@ -1,21 +1,18 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default class AdminDeletePostsConfirmationController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminUserIndex: controller(),
) { username: alias("model.username"),
@controller adminUserIndex; postCount: alias("model.post_count"),
@alias("model.username") username;
@alias("model.post_count") postCount;
onShow() { onShow() {
this.set("value", null); this.set("value", null);
} },
@discourseComputed("username", "postCount") @discourseComputed("username", "postCount")
text(username, postCount) { text(username, postCount) {
@ -23,27 +20,27 @@ export default class AdminDeletePostsConfirmationController extends Controller.e
username, username,
postCount, postCount,
}); });
} },
@discourseComputed("username") @discourseComputed("username")
deleteButtonText(username) { deleteButtonText(username) {
return I18n.t(`admin.user.delete_posts.confirmation.delete`, { return I18n.t(`admin.user.delete_posts.confirmation.delete`, {
username, username,
}); });
} },
@discourseComputed("value", "text") @discourseComputed("value", "text")
deleteDisabled(value, text) { deleteDisabled(value, text) {
return !value || text !== value; return !value || text !== value;
} },
@action @action
confirm() { confirm() {
this.adminUserIndex.send("deleteAllPosts"); this.adminUserIndex.send("deleteAllPosts");
} },
@action @action
close() { close() {
this.send("closeModal"); this.send("closeModal");
} },
} });

View File

@ -1,8 +1,6 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminDeleteUserPostsProgressController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality deletedPercentage: 0,
) { });
deletedPercentage = 0;
}

View File

@ -1,16 +1,13 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { A } from "@ember/array"; import { A } from "@ember/array";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminEditBadgeGroupingsController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality dialog: service(),
) {
@service dialog;
@observes("model") @observes("model")
modelChanged() { modelChanged() {
@ -25,7 +22,7 @@ export default class AdminEditBadgeGroupingsController extends Controller.extend
} }
this.set("workingCopy", copy); this.set("workingCopy", copy);
} },
moveItem(item, delta) { moveItem(item, delta) {
const copy = this.workingCopy; const copy = this.workingCopy;
@ -36,68 +33,55 @@ export default class AdminEditBadgeGroupingsController extends Controller.extend
copy.removeAt(index); copy.removeAt(index);
copy.insertAt(index + delta, item); copy.insertAt(index + delta, item);
} },
@action actions: {
up(item) { up(item) {
this.moveItem(item, -1); this.moveItem(item, -1);
} },
down(item) {
this.moveItem(item, 1);
},
delete(item) {
this.workingCopy.removeObject(item);
},
cancel() {
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
},
edit(item) {
item.set("editing", true);
},
save(item) {
item.set("editing", false);
},
add() {
const obj = this.store.createRecord("badge-grouping", {
editing: true,
name: I18n.t("admin.badges.badge_grouping"),
});
this.workingCopy.pushObject(obj);
},
saveAll() {
let items = this.workingCopy;
const groupIds = items.map((i) => i.get("id") || -1);
const names = items.map((i) => i.get("name"));
@action ajax("/admin/badges/badge_groupings", {
down(item) { data: { ids: groupIds, names },
this.moveItem(item, 1); type: "POST",
} }).then(
(data) => {
@action items = this.model;
delete(item) { items.clear();
this.workingCopy.removeObject(item); data.badge_groupings.forEach((g) => {
} items.pushObject(this.store.createRecord("badge-grouping", g));
});
@action this.setProperties({ model: null, workingCopy: null });
cancel() { this.send("closeModal");
this.setProperties({ model: null, workingCopy: null }); },
this.send("closeModal"); () => this.dialog.alert(I18n.t("generic_error"))
} );
},
@action },
edit(item) { });
item.set("editing", true);
}
@action
save(item) {
item.set("editing", false);
}
@action
add() {
const obj = this.store.createRecord("badge-grouping", {
editing: true,
name: I18n.t("admin.badges.badge_grouping"),
});
this.workingCopy.pushObject(obj);
}
@action
saveAll() {
let items = this.workingCopy;
const groupIds = items.map((i) => i.get("id") || -1);
const names = items.map((i) => i.get("name"));
ajax("/admin/badges/badge_groupings", {
data: { ids: groupIds, names },
type: "POST",
}).then(
(data) => {
items = this.model;
items.clear();
data.badge_groupings.forEach((g) => {
items.pushObject(this.store.createRecord("badge-grouping", g));
});
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
},
() => this.dialog.alert(I18n.t("generic_error"))
);
}
}

View File

@ -5,17 +5,15 @@ import discourseComputed from "discourse-common/utils/decorators";
import { longDate } from "discourse/lib/formatter"; import { longDate } from "discourse/lib/formatter";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminIncomingEmailController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality
) {
@discourseComputed("model.date") @discourseComputed("model.date")
date(d) { date(d) {
return longDate(d); return longDate(d);
} },
load(id) { load(id) {
return IncomingEmail.find(id).then((result) => this.set("model", result)); return IncomingEmail.find(id).then((result) => this.set("model", result));
} },
loadFromBounced(id) { loadFromBounced(id) {
return IncomingEmail.findByBounced(id) return IncomingEmail.findByBounced(id)
@ -24,5 +22,5 @@ export default class AdminIncomingEmailController extends Controller.extend(
this.send("closeModal"); this.send("closeModal");
popupAjaxError(error); popupAjaxError(error);
}); });
} },
} });

View File

@ -1,46 +1,46 @@
import { alias, equal, match } from "@ember/object/computed";
import { COMPONENTS, THEMES } from "admin/models/theme"; import { COMPONENTS, THEMES } from "admin/models/theme";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import { alias, equal, match } from "@ember/object/computed";
import { observes } from "@ember-decorators/object"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes"; import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { action, set } from "@ember/object"; import { set } from "@ember/object";
const MIN_NAME_LENGTH = 4; const MIN_NAME_LENGTH = 4;
export default class AdminInstallThemeController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminCustomizeThemes: controller(),
) { themesController: controller("adminCustomizeThemes"),
@controller adminCustomizeThemes; popular: equal("selection", "popular"),
@controller("adminCustomizeThemes") themesController; local: equal("selection", "local"),
remote: equal("selection", "remote"),
create: equal("selection", "create"),
directRepoInstall: equal("selection", "directRepoInstall"),
selection: "popular",
loading: false,
keyGenUrl: "/admin/themes/generate_key_pair",
importUrl: "/admin/themes/import",
recordType: "theme",
checkPrivate: match("uploadUrl", /^ssh:\/\/.+@.+$|.+@.+:.+$/),
localFile: null,
uploadUrl: null,
uploadName: null,
advancedVisible: false,
selectedType: alias("themesController.currentTab"),
component: equal("selectedType", COMPONENTS),
urlPlaceholder: "https://github.com/discourse/sample_theme",
@equal("selection", "popular") popular; init() {
@equal("selection", "local") local; this._super(...arguments);
@equal("selection", "remote") remote;
@equal("selection", "create") create;
@equal("selection", "directRepoInstall") directRepoInstall;
selection = "popular";
loading = false;
keyGenUrl = "/admin/themes/generate_key_pair";
importUrl = "/admin/themes/import";
recordType = "theme";
@match("uploadUrl", /^ssh:\/\/.+@.+$|.+@.+:.+$/) checkPrivate;
localFile = null;
uploadUrl = null;
uploadName = null;
advancedVisible = false;
@alias("themesController.currentTab") selectedType;
@equal("selectedType", COMPONENTS) component;
urlPlaceholder = "https://github.com/discourse/sample_theme";
createTypes = [ this.createTypes = [
{ name: I18n.t("admin.customize.theme.theme"), value: THEMES }, { name: I18n.t("admin.customize.theme.theme"), value: THEMES },
{ name: I18n.t("admin.customize.theme.component"), value: COMPONENTS }, { name: I18n.t("admin.customize.theme.component"), value: COMPONENTS },
]; ];
},
@discourseComputed("themesController.installedThemes") @discourseComputed("themesController.installedThemes")
themes(installedThemes) { themes(installedThemes) {
@ -52,7 +52,7 @@ export default class AdminInstallThemeController extends Controller.extend(
} }
return t; return t;
}); });
} },
@discourseComputed( @discourseComputed(
"loading", "loading",
@ -78,12 +78,12 @@ export default class AdminInstallThemeController extends Controller.extend(
(isLocal && !localFile) || (isLocal && !localFile) ||
(isCreate && nameTooShort) (isCreate && nameTooShort)
); );
} },
@discourseComputed("name") @discourseComputed("name")
nameTooShort(name) { nameTooShort(name) {
return !name || name.length < MIN_NAME_LENGTH; return !name || name.length < MIN_NAME_LENGTH;
} },
@discourseComputed("component") @discourseComputed("component")
placeholder(component) { placeholder(component) {
@ -92,7 +92,7 @@ export default class AdminInstallThemeController extends Controller.extend(
} else { } else {
return I18n.t("admin.customize.theme.theme_name"); return I18n.t("admin.customize.theme.theme_name");
} }
} },
@observes("checkPrivate") @observes("checkPrivate")
privateWasChecked() { privateWasChecked() {
@ -108,7 +108,7 @@ export default class AdminInstallThemeController extends Controller.extend(
this._keyLoading = false; this._keyLoading = false;
}); });
} }
} },
@discourseComputed("selection", "themeCannotBeInstalled") @discourseComputed("selection", "themeCannotBeInstalled")
submitLabel(selection, themeCannotBeInstalled) { submitLabel(selection, themeCannotBeInstalled) {
@ -119,12 +119,12 @@ export default class AdminInstallThemeController extends Controller.extend(
return `admin.customize.theme.${ return `admin.customize.theme.${
selection === "create" ? "create" : "install" selection === "create" ? "create" : "install"
}`; }`;
} },
@discourseComputed("checkPrivate", "publicKey") @discourseComputed("checkPrivate", "publicKey")
showPublicKey(checkPrivate, publicKey) { showPublicKey(checkPrivate, publicKey) {
return checkPrivate && publicKey; return checkPrivate && publicKey;
} },
onClose() { onClose() {
this.setProperties({ this.setProperties({
@ -140,7 +140,7 @@ export default class AdminInstallThemeController extends Controller.extend(
repoName: null, repoName: null,
repoUrl: null, repoUrl: null,
}); });
} },
themeHasSameUrl(theme, url) { themeHasSameUrl(theme, url) {
const themeUrl = theme.remote_theme && theme.remote_theme.remote_url; const themeUrl = theme.remote_theme && theme.remote_theme.remote_url;
@ -149,101 +149,100 @@ export default class AdminInstallThemeController extends Controller.extend(
url && url &&
url.replace(/\.git$/, "") === themeUrl.replace(/\.git$/, "") url.replace(/\.git$/, "") === themeUrl.replace(/\.git$/, "")
); );
} },
@action actions: {
uploadLocaleFile() { uploadLocaleFile() {
this.set("localFile", $("#file-input")[0].files[0]); this.set("localFile", $("#file-input")[0].files[0]);
} },
@action toggleAdvanced() {
toggleAdvanced() { this.toggleProperty("advancedVisible");
this.toggleProperty("advancedVisible"); },
}
@action installThemeFromList(url) {
installThemeFromList(url) { this.set("uploadUrl", url);
this.set("uploadUrl", url); this.send("installTheme");
this.send("installTheme"); },
}
@action installTheme() {
installTheme() { if (this.create) {
if (this.create) { this.set("loading", true);
this.set("loading", true); const theme = this.store.createRecord(this.recordType);
const theme = this.store.createRecord(this.recordType); theme
theme .save({ name: this.name, component: this.component })
.save({ name: this.name, component: this.component }) .then(() => {
.then(() => { this.themesController.send("addTheme", theme);
this.themesController.send("addTheme", theme); this.send("closeModal");
this.send("closeModal"); })
}) .catch(popupAjaxError)
.catch(popupAjaxError) .finally(() => this.set("loading", false));
.finally(() => this.set("loading", false));
return;
}
let options = {
type: "POST",
};
if (this.local) {
options.processData = false;
options.contentType = false;
options.data = new FormData();
options.data.append("theme", this.localFile);
}
if (this.remote || this.popular || this.directRepoInstall) {
const duplicate = this.themesController.model.content.find((theme) =>
this.themeHasSameUrl(theme, this.uploadUrl)
);
if (duplicate && !this.duplicateRemoteThemeWarning) {
const warning = I18n.t("admin.customize.theme.duplicate_remote_theme", {
name: duplicate.name,
});
this.set("duplicateRemoteThemeWarning", warning);
return; return;
} }
options.data = {
remote: this.uploadUrl, let options = {
branch: this.branch, type: "POST",
public_key: this.publicKey,
}; };
}
// User knows that theme cannot be installed, but they want to continue if (this.local) {
// to force install it. options.processData = false;
if (this.themeCannotBeInstalled) { options.contentType = false;
options.data["force"] = true; options.data = new FormData();
} options.data.append("theme", this.localFile);
}
if (this.get("model.user_id")) { if (this.remote || this.popular || this.directRepoInstall) {
// Used by theme-creator const duplicate = this.themesController.model.content.find((theme) =>
options.data["user_id"] = this.get("model.user_id"); this.themeHasSameUrl(theme, this.uploadUrl)
}
this.set("loading", true);
ajax(this.importUrl, options)
.then((result) => {
const theme = this.store.createRecord(this.recordType, result.theme);
this.adminCustomizeThemes.send("addTheme", theme);
this.send("closeModal");
})
.then(() => {
this.set("publicKey", null);
})
.catch((error) => {
if (!this.publicKey || this.themeCannotBeInstalled) {
return popupAjaxError(error);
}
this.set(
"themeCannotBeInstalled",
I18n.t("admin.customize.theme.force_install")
); );
}) if (duplicate && !this.duplicateRemoteThemeWarning) {
.finally(() => this.set("loading", false)); const warning = I18n.t(
} "admin.customize.theme.duplicate_remote_theme",
} { name: duplicate.name }
);
this.set("duplicateRemoteThemeWarning", warning);
return;
}
options.data = {
remote: this.uploadUrl,
branch: this.branch,
public_key: this.publicKey,
};
}
// User knows that theme cannot be installed, but they want to continue
// to force install it.
if (this.themeCannotBeInstalled) {
options.data["force"] = true;
}
if (this.get("model.user_id")) {
// Used by theme-creator
options.data["user_id"] = this.get("model.user_id");
}
this.set("loading", true);
ajax(this.importUrl, options)
.then((result) => {
const theme = this.store.createRecord(this.recordType, result.theme);
this.adminCustomizeThemes.send("addTheme", theme);
this.send("closeModal");
})
.then(() => {
this.set("publicKey", null);
})
.catch((error) => {
if (!this.publicKey || this.themeCannotBeInstalled) {
return popupAjaxError(error);
}
this.set(
"themeCannotBeInstalled",
I18n.t("admin.customize.theme.force_install")
);
})
.finally(() => this.set("loading", false));
},
},
});

View File

@ -1,21 +1,18 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default class AdminMergeUsersConfirmationController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminUserIndex: controller(),
) { username: alias("model.username"),
@controller adminUserIndex; targetUsername: alias("model.targetUsername"),
@alias("model.username") username;
@alias("model.targetUsername") targetUsername;
onShow() { onShow() {
this.set("value", null); this.set("value", null);
} },
@discourseComputed("username", "targetUsername") @discourseComputed("username", "targetUsername")
text(username, targetUsername) { text(username, targetUsername) {
@ -23,28 +20,28 @@ export default class AdminMergeUsersConfirmationController extends Controller.ex
username, username,
targetUsername, targetUsername,
}); });
} },
@discourseComputed("username") @discourseComputed("username")
mergeButtonText(username) { mergeButtonText(username) {
return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, { return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, {
username, username,
}); });
} },
@discourseComputed("value", "text") @discourseComputed("value", "text")
mergeDisabled(value, text) { mergeDisabled(value, text) {
return !value || text !== value; return !value || text !== value;
} },
@action @action
confirm() { confirm() {
this.adminUserIndex.send("merge", this.targetUsername); this.adminUserIndex.send("merge", this.targetUsername);
this.send("closeModal"); this.send("closeModal");
} },
@action @action
close() { close() {
this.send("closeModal"); this.send("closeModal");
} },
} });

View File

@ -4,18 +4,16 @@ import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
export default class AdminMergeUsersProgressController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality message: I18n.t("admin.user.merging_user"),
) {
message = I18n.t("admin.user.merging_user");
onShow() { onShow() {
this.messageBus.subscribe("/merge_user", this.onMessage); this.messageBus.subscribe("/merge_user", this.onMessage);
} },
onClose() { onClose() {
this.messageBus.unsubscribe("/merge_user", this.onMessage); this.messageBus.unsubscribe("/merge_user", this.onMessage);
} },
@bind @bind
onMessage(data) { onMessage(data) {
@ -32,5 +30,5 @@ export default class AdminMergeUsersProgressController extends Controller.extend
} else if (data.failed) { } else if (data.failed) {
this.set("message", I18n.t("admin.user.merge_failed")); this.set("message", I18n.t("admin.user.merge_failed"));
} }
} },
} });

View File

@ -1,46 +1,43 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action, get } from "@ember/object"; import { action, get } from "@ember/object";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default class AdminMergeUsersPromptController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminUserIndex: controller(),
) { username: alias("model.username"),
@controller adminUserIndex;
@alias("model.username") username;
onShow() { onShow() {
this.set("targetUsername", null); this.set("targetUsername", null);
} },
@discourseComputed("username", "targetUsername") @discourseComputed("username", "targetUsername")
mergeDisabled(username, targetUsername) { mergeDisabled(username, targetUsername) {
return !targetUsername || username === targetUsername; return !targetUsername || username === targetUsername;
} },
@discourseComputed("username") @discourseComputed("username")
mergeButtonText(username) { mergeButtonText(username) {
return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, { return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, {
username, username,
}); });
} },
@action @action
showConfirmation() { showConfirmation() {
this.send("closeModal"); this.send("closeModal");
this.adminUserIndex.send("showMergeConfirmation", this.targetUsername); this.adminUserIndex.send("showMergeConfirmation", this.targetUsername);
} },
@action @action
close() { close() {
this.send("closeModal"); this.send("closeModal");
} },
@action @action
updateUsername(selected) { updateUsername(selected) {
this.set("targetUsername", get(selected, "firstObject")); this.set("targetUsername", get(selected, "firstObject"));
} },
} });

View File

@ -1,31 +1,29 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { extractError } from "discourse/lib/ajax-error"; import { extractError } from "discourse/lib/ajax-error";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import I18n from "I18n"; import I18n from "I18n";
export default class AdminPenalizeUserController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality dialog: service(),
) {
@service dialog;
loadingUser = false; loadingUser: false,
errorMessage = null; errorMessage: null,
penaltyType = null; penaltyType: null,
penalizeUntil = null; penalizeUntil: null,
reason = null; reason: null,
message = null; message: null,
postId = null; postId: null,
postAction = null; postAction: null,
postEdit = null; postEdit: null,
user = null; user: null,
otherUserIds = null; otherUserIds: null,
loading = false; loading: false,
confirmClose = false; confirmClose: false,
onShow() { onShow() {
this.setProperties({ this.setProperties({
@ -46,11 +44,11 @@ export default class AdminPenalizeUserController extends Controller.extend(
message: null, message: null,
confirmClose: false, confirmClose: false,
}); });
} },
finishedSetup() { finishedSetup() {
this.set("penalizeUntil", this.user?.next_penalty); this.set("penalizeUntil", this.user?.next_penalty);
} },
beforeClose() { beforeClose() {
if (this.confirmClose) { if (this.confirmClose) {
@ -75,7 +73,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
return false; return false;
} }
} },
@discourseComputed("penaltyType") @discourseComputed("penaltyType")
modalTitle(penaltyType) { modalTitle(penaltyType) {
@ -84,7 +82,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
} else if (penaltyType === "silence") { } else if (penaltyType === "silence") {
return "admin.user.silence_modal_title"; return "admin.user.silence_modal_title";
} }
} },
@discourseComputed("penaltyType") @discourseComputed("penaltyType")
buttonLabel(penaltyType) { buttonLabel(penaltyType) {
@ -93,7 +91,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
} else if (penaltyType === "silence") { } else if (penaltyType === "silence") {
return "admin.user.silence"; return "admin.user.silence";
} }
} },
@discourseComputed( @discourseComputed(
"user.penalty_counts.suspended", "user.penalty_counts.suspended",
@ -104,7 +102,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
SUSPENDED: suspendedCount, SUSPENDED: suspendedCount,
SILENCED: silencedCount, SILENCED: silencedCount,
}); });
} },
@discourseComputed("penaltyType", "user.canSuspend", "user.canSilence") @discourseComputed("penaltyType", "user.canSuspend", "user.canSilence")
canPenalize(penaltyType, canSuspend, canSilence) { canPenalize(penaltyType, canSuspend, canSilence) {
@ -115,12 +113,12 @@ export default class AdminPenalizeUserController extends Controller.extend(
} }
return false; return false;
} },
@discourseComputed("penalizing", "penalizeUntil", "reason") @discourseComputed("penalizing", "penalizeUntil", "reason")
submitDisabled(penalizing, penalizeUntil, reason) { submitDisabled(penalizing, penalizeUntil, reason) {
return penalizing || isEmpty(penalizeUntil) || !reason || reason.length < 1; return penalizing || isEmpty(penalizeUntil) || !reason || reason.length < 1;
} },
@action @action
async penalizeUser() { async penalizeUser() {
@ -166,5 +164,5 @@ export default class AdminPenalizeUserController extends Controller.extend(
} finally { } finally {
this.set("penalizing", false); this.set("penalizing", false);
} }
} },
} });

View File

@ -1,19 +1,15 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { inject as service } from "@ember/service";
export default class AdminReseedController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality dialog: service(),
) { loading: true,
@service dialog; reseeding: false,
categories: null,
loading = true; topics: null,
reseeding = false;
categories = null;
topics = null;
onShow() { onShow() {
ajax("/admin/customize/reseed") ajax("/admin/customize/reseed")
@ -24,26 +20,27 @@ export default class AdminReseedController extends Controller.extend(
}); });
}) })
.finally(() => this.set("loading", false)); .finally(() => this.set("loading", false));
} },
_extractSelectedIds(items) { _extractSelectedIds(items) {
return items.filter((item) => item.selected).map((item) => item.id); return items.filter((item) => item.selected).map((item) => item.id);
} },
@action actions: {
reseed() { reseed() {
this.set("reseeding", true); this.set("reseeding", true);
ajax("/admin/customize/reseed", { ajax("/admin/customize/reseed", {
data: { data: {
category_ids: this._extractSelectedIds(this.categories), category_ids: this._extractSelectedIds(this.categories),
topic_ids: this._extractSelectedIds(this.topics), topic_ids: this._extractSelectedIds(this.topics),
}, },
type: "POST", type: "POST",
}) })
.catch(() => this.dialog.alert(I18n.t("generic_error"))) .catch(() => this.dialog.alert(I18n.t("generic_error")))
.finally(() => { .finally(() => {
this.set("reseeding", false); this.set("reseeding", false);
this.send("closeModal"); this.send("closeModal");
}); });
} },
} },
});

View File

@ -1,6 +1,4 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminStaffActionLogDetailsController extends Controller.extend( export default Controller.extend(ModalFunctionality);
ModalFunctionality
) {}

View File

@ -1,39 +1,35 @@
import { action } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminStartBackupController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality adminBackupsLogs: controller(),
) {
@controller adminBackupsLogs;
@discourseComputed @discourseComputed
warningMessage() { warningMessage() {
// this is never shown here, but we may want to show different // this is never shown here, but we may want to show different
// messages in plugins // messages in plugins
return ""; return "";
} },
@discourseComputed @discourseComputed
yesLabel() { yesLabel() {
return "yes_value"; return "yes_value";
} },
@action actions: {
startBackupWithUploads() { startBackupWithUploads() {
this.send("closeModal"); this.send("closeModal");
this.send("startBackup", true); this.send("startBackup", true);
} },
@action startBackupWithoutUploads() {
startBackupWithoutUploads() { this.send("closeModal");
this.send("closeModal"); this.send("startBackup", false);
this.send("startBackup", false); },
}
@action cancel() {
cancel() { this.send("closeModal");
this.send("closeModal"); },
} },
} });

View File

@ -2,9 +2,7 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
export default class AdminThemeChangeController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality
) {
loadDiff() { loadDiff() {
this.set("loading", true); this.set("loading", true);
ajax( ajax(
@ -13,5 +11,5 @@ export default class AdminThemeChangeController extends Controller.extend(
this.set("loading", false); this.set("loading", false);
this.set("diff", diff.side_by_side); this.set("diff", diff.side_by_side);
}); });
} },
} });

View File

@ -1,32 +1,30 @@
import { observes, on } from "@ember-decorators/object"; import { observes, on } from "discourse-common/utils/decorators";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { action } from "@ember/object"; import { action } from "@ember/object";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminUploadedImageListController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality
) {
@on("init") @on("init")
@observes("model.value") @observes("model.value")
_setup() { _setup() {
const value = this.get("model.value"); const value = this.get("model.value");
this.set("images", value && value.length ? value.split("|") : []); this.set("images", value && value.length ? value.split("|") : []);
} },
@action @action
remove(url, event) { remove(url, event) {
event?.preventDefault(); event?.preventDefault();
this.images.removeObject(url); this.images.removeObject(url);
} },
@action actions: {
uploadDone({ url }) { uploadDone({ url }) {
this.images.addObject(url); this.images.addObject(url);
} },
@action close() {
close() { this.save(this.images.join("|"));
this.save(this.images.join("|")); this.send("closeModal");
this.send("closeModal"); },
} },
} });

View File

@ -1,19 +1,16 @@
import { equal } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { equal } from "@ember/object/computed";
import { import {
createWatchedWordRegExp, createWatchedWordRegExp,
toWatchedWord, toWatchedWord,
} from "discourse-common/utils/watched-words"; } from "discourse-common/utils/watched-words";
export default class AdminWatchedWordTestController extends Controller.extend( export default Controller.extend(ModalFunctionality, {
ModalFunctionality isReplace: equal("model.nameKey", "replace"),
) { isTag: equal("model.nameKey", "tag"),
@equal("model.nameKey", "replace") isReplace; isLink: equal("model.nameKey", "link"),
@equal("model.nameKey", "tag") isTag;
@equal("model.nameKey", "link") isLink;
@discourseComputed( @discourseComputed(
"value", "value",
@ -74,5 +71,5 @@ export default class AdminWatchedWordTestController extends Controller.extend(
return matches; return matches;
} }
} },
} });

View File

@ -2,7 +2,4 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import ModalUpdateExistingUsers from "discourse/mixins/modal-update-existing-users"; import ModalUpdateExistingUsers from "discourse/mixins/modal-update-existing-users";
export default class SiteSettingDefaultCategoriesController extends Controller.extend( export default Controller.extend(ModalFunctionality, ModalUpdateExistingUsers);
ModalFunctionality,
ModalUpdateExistingUsers
) {}

View File

@ -2,7 +2,7 @@ import Helper from "@ember/component/helper";
import { iconHTML } from "discourse-common/lib/icon-library"; import { iconHTML } from "discourse-common/lib/icon-library";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
export default class DispositionIcon extends Helper { export default Helper.extend({
compute([disposition]) { compute([disposition]) {
if (!disposition) { if (!disposition) {
return null; return null;
@ -24,5 +24,5 @@ export default class DispositionIcon extends Helper {
} }
} }
return htmlSafe(iconHTML(icon, { title })); return htmlSafe(iconHTML(icon, { title }));
} },
} });

View File

@ -7,17 +7,19 @@ const GENERAL_ATTRIBUTES = [
"release_notes_link", "release_notes_link",
]; ];
export default class AdminDashboard extends EmberObject { const AdminDashboard = EmberObject.extend({});
static fetch() {
AdminDashboard.reopenClass({
fetch() {
return ajax("/admin/dashboard.json").then((json) => { return ajax("/admin/dashboard.json").then((json) => {
const model = AdminDashboard.create(); const model = AdminDashboard.create();
model.set("version_check", json.version_check); model.set("version_check", json.version_check);
return model; return model;
}); });
} },
static fetchGeneral() { fetchGeneral() {
return ajax("/admin/dashboard/general.json").then((json) => { return ajax("/admin/dashboard/general.json").then((json) => {
const model = AdminDashboard.create(); const model = AdminDashboard.create();
@ -32,13 +34,15 @@ export default class AdminDashboard extends EmberObject {
return model; return model;
}); });
} },
static fetchProblems() { fetchProblems() {
return ajax("/admin/dashboard/problems.json").then((json) => { return ajax("/admin/dashboard/problems.json").then((json) => {
const model = AdminDashboard.create(json); const model = AdminDashboard.create(json);
model.set("loaded", true); model.set("loaded", true);
return model; return model;
}); });
} },
} });
export default AdminDashboard;

View File

@ -10,30 +10,14 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
import { propertyNotEqual } from "discourse/lib/computed"; import { propertyNotEqual } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url"; import { userPath } from "discourse/lib/url";
export default class AdminUser extends User { const wrapAdmin = (user) => (user ? AdminUser.create(user) : null);
static find(user_id) {
return ajax(`/admin/users/${user_id}.json`).then((result) => {
result.loadedDetails = true;
return AdminUser.create(result);
});
}
static findAll(query, userFilter) { const AdminUser = User.extend({
return ajax(`/admin/users/list/${query}.json`, { adminUserView: true,
data: userFilter, customGroups: filter("groups", (g) => !g.automatic && Group.create(g)),
}).then((users) => users.map((u) => AdminUser.create(u))); automaticGroups: filter("groups", (g) => g.automatic && Group.create(g)),
}
adminUserView = true; canViewProfile: or("active", "staged"),
@filter("groups", (g) => !g.automatic && Group.create(g)) customGroups;
@filter("groups", (g) => g.automatic && Group.create(g)) automaticGroups;
@or("active", "staged") canViewProfile;
@gt("bounce_score", 0) canResetBounceScore;
@propertyNotEqual("originalTrustLevel", "trust_level") dirty;
@lt("trust_level", 4) canLockTrustLevel;
@not("staff") canSuspend;
@not("staff") canSilence;
@discourseComputed("bounce_score", "reset_bounce_score_after") @discourseComputed("bounce_score", "reset_bounce_score_after")
bounceScore(bounce_score, reset_bounce_score_after) { bounceScore(bounce_score, reset_bounce_score_after) {
@ -44,7 +28,7 @@ export default class AdminUser extends User {
} else { } else {
return bounce_score; return bounce_score;
} }
} },
@discourseComputed("bounce_score") @discourseComputed("bounce_score")
bounceScoreExplanation(bounce_score) { bounceScoreExplanation(bounce_score) {
@ -55,12 +39,14 @@ export default class AdminUser extends User {
} else { } else {
return I18n.t("admin.user.bounce_score_explanation.threshold_reached"); return I18n.t("admin.user.bounce_score_explanation.threshold_reached");
} }
} },
@discourseComputed @discourseComputed
bounceLink() { bounceLink() {
return getURL("/admin/email/bounced"); return getURL("/admin/email/bounced");
} },
canResetBounceScore: gt("bounce_score", 0),
resetBounceScore() { resetBounceScore() {
return ajax(`/admin/users/${this.id}/reset_bounce_score`, { return ajax(`/admin/users/${this.id}/reset_bounce_score`, {
@ -71,14 +57,14 @@ export default class AdminUser extends User {
reset_bounce_score_after: null, reset_bounce_score_after: null,
}) })
); );
} },
groupAdded(added) { groupAdded(added) {
return ajax(`/admin/users/${this.id}/groups`, { return ajax(`/admin/users/${this.id}/groups`, {
type: "POST", type: "POST",
data: { group_id: added.id }, data: { group_id: added.id },
}).then(() => this.groups.pushObject(added)); }).then(() => this.groups.pushObject(added));
} },
groupRemoved(groupId) { groupRemoved(groupId) {
return ajax(`/admin/users/${this.id}/groups/${groupId}`, { return ajax(`/admin/users/${this.id}/groups/${groupId}`, {
@ -89,13 +75,13 @@ export default class AdminUser extends User {
this.set("primary_group_id", null); this.set("primary_group_id", null);
} }
}); });
} },
deleteAllPosts() { deleteAllPosts() {
return ajax(`/admin/users/${this.get("id")}/delete_posts_batch`, { return ajax(`/admin/users/${this.get("id")}/delete_posts_batch`, {
type: "PUT", type: "PUT",
}); });
} },
revokeAdmin() { revokeAdmin() {
return ajax(`/admin/users/${this.id}/revoke_admin`, { return ajax(`/admin/users/${this.id}/revoke_admin`, {
@ -111,7 +97,7 @@ export default class AdminUser extends User {
can_delete_all_posts: resp.can_delete_all_posts, can_delete_all_posts: resp.can_delete_all_posts,
}); });
}); });
} },
grantAdmin(data) { grantAdmin(data) {
return ajax(`/admin/users/${this.id}/grant_admin`, { return ajax(`/admin/users/${this.id}/grant_admin`, {
@ -128,7 +114,7 @@ export default class AdminUser extends User {
return resp; return resp;
}); });
} },
revokeModeration() { revokeModeration() {
return ajax(`/admin/users/${this.id}/revoke_moderation`, { return ajax(`/admin/users/${this.id}/revoke_moderation`, {
@ -144,7 +130,7 @@ export default class AdminUser extends User {
}); });
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
grantModeration() { grantModeration() {
return ajax(`/admin/users/${this.id}/grant_moderation`, { return ajax(`/admin/users/${this.id}/grant_moderation`, {
@ -160,7 +146,7 @@ export default class AdminUser extends User {
}); });
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
disableSecondFactor() { disableSecondFactor() {
return ajax(`/admin/users/${this.id}/disable_second_factor`, { return ajax(`/admin/users/${this.id}/disable_second_factor`, {
@ -170,7 +156,7 @@ export default class AdminUser extends User {
this.set("second_factor_enabled", false); this.set("second_factor_enabled", false);
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
approve(approvedBy) { approve(approvedBy) {
return ajax(`/admin/users/${this.id}/approve`, { return ajax(`/admin/users/${this.id}/approve`, {
@ -182,76 +168,83 @@ export default class AdminUser extends User {
approved_by: approvedBy, approved_by: approvedBy,
}); });
}); });
} },
setOriginalTrustLevel() { setOriginalTrustLevel() {
this.set("originalTrustLevel", this.trust_level); this.set("originalTrustLevel", this.trust_level);
} },
dirty: propertyNotEqual("originalTrustLevel", "trust_level"),
saveTrustLevel() { saveTrustLevel() {
return ajax(`/admin/users/${this.id}/trust_level`, { return ajax(`/admin/users/${this.id}/trust_level`, {
type: "PUT", type: "PUT",
data: { level: this.trust_level }, data: { level: this.trust_level },
}); });
} },
restoreTrustLevel() { restoreTrustLevel() {
this.set("trust_level", this.originalTrustLevel); this.set("trust_level", this.originalTrustLevel);
} },
lockTrustLevel(locked) { lockTrustLevel(locked) {
return ajax(`/admin/users/${this.id}/trust_level_lock`, { return ajax(`/admin/users/${this.id}/trust_level_lock`, {
type: "PUT", type: "PUT",
data: { locked: !!locked }, data: { locked: !!locked },
}); });
} },
canLockTrustLevel: lt("trust_level", 4),
canSuspend: not("staff"),
canSilence: not("staff"),
@discourseComputed("suspended_till", "suspended_at") @discourseComputed("suspended_till", "suspended_at")
suspendDuration(suspendedTill, suspendedAt) { suspendDuration(suspendedTill, suspendedAt) {
suspendedAt = moment(suspendedAt); suspendedAt = moment(suspendedAt);
suspendedTill = moment(suspendedTill); suspendedTill = moment(suspendedTill);
return suspendedAt.format("L") + " - " + suspendedTill.format("L"); return suspendedAt.format("L") + " - " + suspendedTill.format("L");
} },
suspend(data) { suspend(data) {
return ajax(`/admin/users/${this.id}/suspend`, { return ajax(`/admin/users/${this.id}/suspend`, {
type: "PUT", type: "PUT",
data, data,
}).then((result) => this.setProperties(result.suspension)); }).then((result) => this.setProperties(result.suspension));
} },
unsuspend() { unsuspend() {
return ajax(`/admin/users/${this.id}/unsuspend`, { return ajax(`/admin/users/${this.id}/unsuspend`, {
type: "PUT", type: "PUT",
}).then((result) => this.setProperties(result.suspension)); }).then((result) => this.setProperties(result.suspension));
} },
logOut() { logOut() {
return ajax("/admin/users/" + this.id + "/log_out", { return ajax("/admin/users/" + this.id + "/log_out", {
type: "POST", type: "POST",
data: { username_or_email: this.username }, data: { username_or_email: this.username },
}); });
} },
impersonate() { impersonate() {
return ajax("/admin/impersonate", { return ajax("/admin/impersonate", {
type: "POST", type: "POST",
data: { username_or_email: this.username }, data: { username_or_email: this.username },
}); });
} },
activate() { activate() {
return ajax(`/admin/users/${this.id}/activate`, { return ajax(`/admin/users/${this.id}/activate`, {
type: "PUT", type: "PUT",
}); });
} },
deactivate() { deactivate() {
return ajax(`/admin/users/${this.id}/deactivate`, { return ajax(`/admin/users/${this.id}/deactivate`, {
type: "PUT", type: "PUT",
data: { context: document.location.pathname }, data: { context: document.location.pathname },
}); });
} },
unsilence() { unsilence() {
this.set("silencingUser", true); this.set("silencingUser", true);
@ -261,7 +254,7 @@ export default class AdminUser extends User {
}) })
.then((result) => this.setProperties(result.unsilence)) .then((result) => this.setProperties(result.unsilence))
.finally(() => this.set("silencingUser", false)); .finally(() => this.set("silencingUser", false));
} },
silence(data) { silence(data) {
this.set("silencingUser", true); this.set("silencingUser", true);
@ -272,20 +265,20 @@ export default class AdminUser extends User {
}) })
.then((result) => this.setProperties(result.silence)) .then((result) => this.setProperties(result.silence))
.finally(() => this.set("silencingUser", false)); .finally(() => this.set("silencingUser", false));
} },
sendActivationEmail() { sendActivationEmail() {
return ajax(userPath("action/send_activation_email"), { return ajax(userPath("action/send_activation_email"), {
type: "POST", type: "POST",
data: { username: this.username }, data: { username: this.username },
}); });
} },
anonymize() { anonymize() {
return ajax(`/admin/users/${this.id}/anonymize.json`, { return ajax(`/admin/users/${this.id}/anonymize.json`, {
type: "PUT", type: "PUT",
}); });
} },
destroy(formData) { destroy(formData) {
return ajax(`/admin/users/${this.id}.json`, { return ajax(`/admin/users/${this.id}.json`, {
@ -302,14 +295,14 @@ export default class AdminUser extends User {
.catch(() => { .catch(() => {
this.find(this.id).then((u) => this.setProperties(u)); this.find(this.id).then((u) => this.setProperties(u));
}); });
} },
merge(formData) { merge(formData) {
return ajax(`/admin/users/${this.id}/merge.json`, { return ajax(`/admin/users/${this.id}/merge.json`, {
type: "POST", type: "POST",
data: formData, data: formData,
}); });
} },
loadDetails() { loadDetails() {
if (this.loadedDetails) { if (this.loadedDetails) {
@ -320,29 +313,23 @@ export default class AdminUser extends User {
const userProperties = Object.assign(result, { loadedDetails: true }); const userProperties = Object.assign(result, { loadedDetails: true });
this.setProperties(userProperties); this.setProperties(userProperties);
}); });
} },
@discourseComputed("tl3_requirements") @discourseComputed("tl3_requirements")
tl3Requirements(requirements) { tl3Requirements(requirements) {
if (requirements) { if (requirements) {
return this.store.createRecord("tl3Requirements", requirements); return this.store.createRecord("tl3Requirements", requirements);
} }
} },
@discourseComputed("suspended_by") @discourseComputed("suspended_by")
suspendedBy(user) { suspendedBy: wrapAdmin,
return user ? AdminUser.create(user) : null;
}
@discourseComputed("silenced_by") @discourseComputed("silenced_by")
silencedBy(user) { silencedBy: wrapAdmin,
return user ? AdminUser.create(user) : null;
}
@discourseComputed("approved_by") @discourseComputed("approved_by")
approvedBy(user) { approvedBy: wrapAdmin,
return user ? AdminUser.create(user) : null;
}
deleteSSORecord() { deleteSSORecord() {
return ajax(`/admin/users/${this.id}/sso_record.json`, { return ajax(`/admin/users/${this.id}/sso_record.json`, {
@ -352,5 +339,22 @@ export default class AdminUser extends User {
this.set("single_sign_on_record", null); this.set("single_sign_on_record", null);
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
} },
} });
AdminUser.reopenClass({
find(user_id) {
return ajax(`/admin/users/${user_id}.json`).then((result) => {
result.loadedDetails = true;
return AdminUser.create(result);
});
},
findAll(query, userFilter) {
return ajax(`/admin/users/list/${query}.json`, {
data: userFilter,
}).then((users) => users.map((u) => AdminUser.create(u)));
},
});
export default AdminUser;

View File

@ -1,26 +1,24 @@
import { computed } from "@ember/object";
import AdminUser from "admin/models/admin-user"; import AdminUser from "admin/models/admin-user";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed"; import { fmt } from "discourse/lib/computed";
export default class ApiKey extends RestModel { const ApiKey = RestModel.extend({
@fmt("truncated_key", "%@...") truncatedKey; user: computed("_user", {
get() {
@computed("_user") return this._user;
get user() { },
return this._user; set(key, value) {
} if (value && !(value instanceof AdminUser)) {
this.set("_user", AdminUser.create(value));
set user(value) { } else {
if (value && !(value instanceof AdminUser)) { this.set("_user", value);
this.set("_user", AdminUser.create(value)); }
} else { return this._user;
this.set("_user", value); },
} }),
return this._user;
}
@discourseComputed("description") @discourseComputed("description")
shortDescription(description) { shortDescription(description) {
@ -28,28 +26,32 @@ export default class ApiKey extends RestModel {
return description; return description;
} }
return `${description.substring(0, 40)}...`; return `${description.substring(0, 40)}...`;
} },
truncatedKey: fmt("truncated_key", "%@..."),
revoke() { revoke() {
return ajax(`${this.basePath}/revoke`, { return ajax(`${this.basePath}/revoke`, {
type: "POST", type: "POST",
}).then((result) => this.setProperties(result.api_key)); }).then((result) => this.setProperties(result.api_key));
} },
undoRevoke() { undoRevoke() {
return ajax(`${this.basePath}/undo-revoke`, { return ajax(`${this.basePath}/undo-revoke`, {
type: "POST", type: "POST",
}).then((result) => this.setProperties(result.api_key)); }).then((result) => this.setProperties(result.api_key));
} },
createProperties() { createProperties() {
return this.getProperties("description", "username", "scopes"); return this.getProperties("description", "username", "scopes");
} },
@discourseComputed() @discourseComputed()
basePath() { basePath() {
return this.store return this.store
.adapterFor("api-key") .adapterFor("api-key")
.pathFor(this.store, "api-key", this.id); .pathFor(this.store, "api-key", this.id);
} },
} });
export default ApiKey;

View File

@ -1,12 +1,12 @@
import { not } from "@ember/object/computed";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
export default class BackupStatus extends EmberObject { export default EmberObject.extend({
@not("restoreEnabled") restoreDisabled; restoreDisabled: not("restoreEnabled"),
@discourseComputed("allowRestore", "isOperationRunning") @discourseComputed("allowRestore", "isOperationRunning")
restoreEnabled(allowRestore, isOperationRunning) { restoreEnabled(allowRestore, isOperationRunning) {
return allowRestore && !isOperationRunning; return allowRestore && !isOperationRunning;
} },
} });

View File

@ -2,12 +2,25 @@ import EmberObject from "@ember/object";
import MessageBus from "message-bus-client"; import MessageBus from "message-bus-client";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
export default class Backup extends EmberObject { const Backup = EmberObject.extend({
static find() { destroy() {
return ajax("/admin/backups.json"); return ajax("/admin/backups/" + this.filename, { type: "DELETE" });
} },
static start(withUploads) { restore() {
return ajax("/admin/backups/" + this.filename + "/restore", {
type: "POST",
data: { client_id: MessageBus.clientId },
});
},
});
Backup.reopenClass({
find() {
return ajax("/admin/backups.json");
},
start(withUploads) {
if (withUploads === undefined) { if (withUploads === undefined) {
withUploads = true; withUploads = true;
} }
@ -18,28 +31,19 @@ export default class Backup extends EmberObject {
client_id: MessageBus.clientId, client_id: MessageBus.clientId,
}, },
}); });
} },
static cancel() { cancel() {
return ajax("/admin/backups/cancel.json", { return ajax("/admin/backups/cancel.json", {
type: "DELETE", type: "DELETE",
}); });
} },
static rollback() { rollback() {
return ajax("/admin/backups/rollback.json", { return ajax("/admin/backups/rollback.json", {
type: "POST", type: "POST",
}); });
} },
});
destroy() { export default Backup;
return ajax("/admin/backups/" + this.filename, { type: "DELETE" });
}
restore() {
return ajax("/admin/backups/" + this.filename + "/restore", {
type: "POST",
data: { client_id: MessageBus.clientId },
});
}
}

View File

@ -1,19 +1,19 @@
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed, {
import { observes, on } from "@ember-decorators/object"; observes,
on,
} from "discourse-common/utils/decorators";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { propertyNotEqual } from "discourse/lib/computed"; import { propertyNotEqual } from "discourse/lib/computed";
export default class ColorSchemeColor extends EmberObject { const ColorSchemeColor = EmberObject.extend({
// Whether the current value is different than Discourse's default color scheme.
@propertyNotEqual("hex", "default_hex") overridden;
@on("init") @on("init")
startTrackingChanges() { startTrackingChanges() {
this.set("originals", { hex: this.hex || "FFFFFF" }); this.set("originals", { hex: this.hex || "FFFFFF" });
// force changed property to be recalculated // force changed property to be recalculated
this.notifyPropertyChange("hex"); this.notifyPropertyChange("hex");
} },
// Whether value has changed since it was last saved. // Whether value has changed since it was last saved.
@discourseComputed("hex") @discourseComputed("hex")
@ -26,23 +26,26 @@ export default class ColorSchemeColor extends EmberObject {
} }
return false; return false;
} },
// Whether the current value is different than Discourse's default color scheme.
overridden: propertyNotEqual("hex", "default_hex"),
// Whether the saved value is different than Discourse's default color scheme. // Whether the saved value is different than Discourse's default color scheme.
@discourseComputed("default_hex", "hex") @discourseComputed("default_hex", "hex")
savedIsOverriden(defaultHex) { savedIsOverriden(defaultHex) {
return this.originals.hex !== defaultHex; return this.originals.hex !== defaultHex;
} },
revert() { revert() {
this.set("hex", this.default_hex); this.set("hex", this.default_hex);
} },
undo() { undo() {
if (this.originals) { if (this.originals) {
this.set("hex", this.originals.hex); this.set("hex", this.originals.hex);
} }
} },
@discourseComputed("name") @discourseComputed("name")
translatedName(name) { translatedName(name) {
@ -51,7 +54,7 @@ export default class ColorSchemeColor extends EmberObject {
} else { } else {
return name; return name;
} }
} },
@discourseComputed("name") @discourseComputed("name")
description(name) { description(name) {
@ -60,7 +63,7 @@ export default class ColorSchemeColor extends EmberObject {
} else { } else {
return ""; return "";
} }
} },
/** /**
brightness returns a number between 0 (darkest) to 255 (brightest). brightness returns a number between 0 (darkest) to 255 (brightest).
@ -87,17 +90,19 @@ export default class ColorSchemeColor extends EmberObject {
1000 1000
); );
} }
} },
@observes("hex") @observes("hex")
hexValueChanged() { hexValueChanged() {
if (this.hex) { if (this.hex) {
this.set("hex", this.hex.toString().replace(/[^0-9a-fA-F]/g, "")); this.set("hex", this.hex.toString().replace(/[^0-9a-fA-F]/g, ""));
} }
} },
@discourseComputed("hex") @discourseComputed("hex")
valid(hex) { valid(hex) {
return hex.match(/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/) !== null; return hex.match(/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/) !== null;
} },
} });
export default ColorSchemeColor;

View File

@ -1,4 +1,3 @@
import { not } from "@ember/object/computed";
import { A } from "@ember/array"; import { A } from "@ember/array";
import ArrayProxy from "@ember/array/proxy"; import ArrayProxy from "@ember/array/proxy";
import ColorSchemeColor from "admin/models/color-scheme-color"; import ColorSchemeColor from "admin/models/color-scheme-color";
@ -6,56 +5,26 @@ import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
class ColorSchemes extends ArrayProxy {} const ColorScheme = EmberObject.extend({
export default class ColorScheme extends EmberObject {
static findAll() {
const colorSchemes = ColorSchemes.create({ content: [], loading: true });
return ajax("/admin/color_schemes").then((all) => {
all.forEach((colorScheme) => {
colorSchemes.pushObject(
ColorScheme.create({
id: colorScheme.id,
name: colorScheme.name,
is_base: colorScheme.is_base,
theme_id: colorScheme.theme_id,
theme_name: colorScheme.theme_name,
base_scheme_id: colorScheme.base_scheme_id,
user_selectable: colorScheme.user_selectable,
colors: colorScheme.colors.map((c) => {
return ColorSchemeColor.create({
name: c.name,
hex: c.hex,
default_hex: c.default_hex,
is_advanced: c.is_advanced,
});
}),
})
);
});
return colorSchemes;
});
}
@not("id") newRecord;
init() { init() {
super.init(...arguments); this._super(...arguments);
this.startTrackingChanges(); this.startTrackingChanges();
} },
@discourseComputed @discourseComputed
description() { description() {
return "" + this.name; return "" + this.name;
} },
startTrackingChanges() { startTrackingChanges() {
this.set("originals", { this.set("originals", {
name: this.name, name: this.name,
user_selectable: this.user_selectable, user_selectable: this.user_selectable,
}); });
} },
schemeJson() { schemeJson() {
const buffer = []; const buffer = [];
@ -64,7 +33,7 @@ export default class ColorScheme extends EmberObject {
}); });
return [`"${this.name}": {`, buffer.join(",\n"), "}"].join("\n"); return [`"${this.name}": {`, buffer.join(",\n"), "}"].join("\n");
} },
copy() { copy() {
const newScheme = ColorScheme.create({ const newScheme = ColorScheme.create({
@ -78,7 +47,7 @@ export default class ColorScheme extends EmberObject {
); );
}); });
return newScheme; return newScheme;
} },
@discourseComputed( @discourseComputed(
"name", "name",
@ -101,7 +70,7 @@ export default class ColorScheme extends EmberObject {
} }
return false; return false;
} },
@discourseComputed("changed") @discourseComputed("changed")
disableSave(changed) { disableSave(changed) {
@ -110,7 +79,9 @@ export default class ColorScheme extends EmberObject {
} }
return !changed || this.saving || this.colors.any((c) => !c.get("valid")); return !changed || this.saving || this.colors.any((c) => !c.get("valid"));
} },
newRecord: not("id"),
save(opts) { save(opts) {
if (this.is_base || this.disableSave) { if (this.is_base || this.disableSave) {
@ -153,7 +124,7 @@ export default class ColorScheme extends EmberObject {
this.setProperties({ savingStatus: I18n.t("saved"), saving: false }); this.setProperties({ savingStatus: I18n.t("saved"), saving: false });
this.notifyPropertyChange("description"); this.notifyPropertyChange("description");
}); });
} },
updateUserSelectable(value) { updateUserSelectable(value) {
if (!this.id) { if (!this.id) {
@ -166,11 +137,45 @@ export default class ColorScheme extends EmberObject {
dataType: "json", dataType: "json",
contentType: "application/json", contentType: "application/json",
}); });
} },
destroy() { destroy() {
if (this.id) { if (this.id) {
return ajax(`/admin/color_schemes/${this.id}`, { type: "DELETE" }); return ajax(`/admin/color_schemes/${this.id}`, { type: "DELETE" });
} }
} },
} });
const ColorSchemes = ArrayProxy.extend({});
ColorScheme.reopenClass({
findAll() {
const colorSchemes = ColorSchemes.create({ content: [], loading: true });
return ajax("/admin/color_schemes").then((all) => {
all.forEach((colorScheme) => {
colorSchemes.pushObject(
ColorScheme.create({
id: colorScheme.id,
name: colorScheme.name,
is_base: colorScheme.is_base,
theme_id: colorScheme.theme_id,
theme_name: colorScheme.theme_name,
base_scheme_id: colorScheme.base_scheme_id,
user_selectable: colorScheme.user_selectable,
colors: colorScheme.colors.map((c) => {
return ColorSchemeColor.create({
name: c.name,
hex: c.hex,
default_hex: c.default_hex,
is_advanced: c.is_advanced,
});
}),
})
);
});
return colorSchemes;
});
},
});
export default ColorScheme;

View File

@ -3,8 +3,10 @@ import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
export default class EmailLog extends EmberObject { const EmailLog = EmberObject.extend({});
static create(attrs) {
EmailLog.reopenClass({
create(attrs) {
attrs = attrs || {}; attrs = attrs || {};
if (attrs.user) { if (attrs.user) {
@ -15,10 +17,10 @@ export default class EmailLog extends EmberObject {
attrs.post_url = getURL(attrs.post_url); attrs.post_url = getURL(attrs.post_url);
} }
return super.create(attrs); return this._super(attrs);
} },
static findAll(filter, offset) { findAll(filter, offset) {
filter = filter || {}; filter = filter || {};
offset = offset || 0; offset = offset || 0;
@ -28,5 +30,7 @@ export default class EmailLog extends EmberObject {
return ajax(`/admin/email/${status}.json?offset=${offset}`, { return ajax(`/admin/email/${status}.json?offset=${offset}`, {
data: filter, data: filter,
}).then((logs) => logs.map((log) => EmailLog.create(log))); }).then((logs) => logs.map((log) => EmailLog.create(log)));
} },
} });
export default EmailLog;

View File

@ -1,21 +1,25 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
export default class EmailPreview extends EmberObject { const EmailPreview = EmberObject.extend({});
static findDigest(username, lastSeenAt) {
return ajax("/admin/email/preview-digest.json", {
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username },
}).then((result) => EmailPreview.create(result));
}
static sendDigest(username, lastSeenAt, email) {
return ajax("/admin/email/send-digest.json", {
type: "POST",
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
});
}
}
export function oneWeekAgo() { export function oneWeekAgo() {
return moment().locale("en").subtract(7, "days").format("YYYY-MM-DD"); return moment().locale("en").subtract(7, "days").format("YYYY-MM-DD");
} }
EmailPreview.reopenClass({
findDigest(username, lastSeenAt) {
return ajax("/admin/email/preview-digest.json", {
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username },
}).then((result) => EmailPreview.create(result));
},
sendDigest(username, lastSeenAt, email) {
return ajax("/admin/email/send-digest.json", {
type: "POST",
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
});
},
});
export default EmailPreview;

View File

@ -1,10 +1,14 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
export default class EmailSettings extends EmberObject { const EmailSettings = EmberObject.extend({});
static find() {
EmailSettings.reopenClass({
find() {
return ajax("/admin/email.json").then(function (settings) { return ajax("/admin/email.json").then(function (settings) {
return EmailSettings.create(settings); return EmailSettings.create(settings);
}); });
} },
} });
export default EmailSettings;

View File

@ -1,10 +1,10 @@
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
export default class EmailStyle extends RestModel { export default RestModel.extend({
changed = false; changed: false,
setField(fieldName, value) { setField(fieldName, value) {
this.set(`${fieldName}`, value); this.set(`${fieldName}`, value);
this.set("changed", true); this.set("changed", true);
} },
} });

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