DEV: removes old dashboard (#7295)

This commit is contained in:
Joffrey JAFFEUX 2019-04-01 12:39:49 +02:00 committed by GitHub
parent d81f3ee2c2
commit e986e96227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 316 additions and 912 deletions

View File

@ -1,6 +1,6 @@
import { setting } from "discourse/lib/computed"; import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
import AdminDashboardNext from "admin/models/admin-dashboard-next"; import AdminDashboard from "admin/models/admin-dashboard";
import Report from "admin/models/report"; import Report from "admin/models/report";
import PeriodComputationMixin from "admin/mixins/period-computation"; import PeriodComputationMixin from "admin/mixins/period-computation";
@ -88,12 +88,12 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
) { ) {
this.set("isLoading", true); this.set("isLoading", true);
AdminDashboardNext.fetchGeneral() AdminDashboard.fetchGeneral()
.then(adminDashboardNextModel => { .then(adminDashboardModel => {
this.setProperties({ this.setProperties({
dashboardFetchedAt: new Date(), dashboardFetchedAt: new Date(),
model: adminDashboardNextModel, model: adminDashboardModel,
reports: Ember.makeArray(adminDashboardNextModel.reports).map(x => reports: Ember.makeArray(adminDashboardModel.reports).map(x =>
Report.create(x) Report.create(x)
) )
}); });

View File

@ -1,90 +0,0 @@
import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";
import AdminDashboardNext from "admin/models/admin-dashboard-next";
import VersionCheck from "admin/models/version-check";
const PROBLEMS_CHECK_MINUTES = 1;
export default Ember.Controller.extend({
isLoading: false,
dashboardFetchedAt: null,
exceptionController: Ember.inject.controller("exception"),
showVersionChecks: setting("version_checks"),
@computed("problems.length")
foundProblems(problemsLength) {
return this.currentUser.get("admin") && (problemsLength || 0) > 0;
},
fetchProblems() {
if (this.get("isLoadingProblems")) return;
if (
!this.get("problemsFetchedAt") ||
moment()
.subtract(PROBLEMS_CHECK_MINUTES, "minutes")
.toDate() > this.get("problemsFetchedAt")
) {
this._loadProblems();
}
},
fetchDashboard() {
const versionChecks = this.siteSettings.version_checks;
if (this.get("isLoading") || !versionChecks) return;
if (
!this.get("dashboardFetchedAt") ||
moment()
.subtract(30, "minutes")
.toDate() > this.get("dashboardFetchedAt")
) {
this.set("isLoading", true);
AdminDashboardNext.fetch()
.then(model => {
let properties = {
dashboardFetchedAt: new Date()
};
if (versionChecks) {
properties.versionCheck = VersionCheck.create(model.version_check);
}
this.setProperties(properties);
})
.catch(e => {
this.get("exceptionController").set("thrown", e.jqXHR);
this.replaceRoute("exception");
})
.finally(() => {
this.set("isLoading", false);
});
}
},
_loadProblems() {
this.setProperties({
loadingProblems: true,
problemsFetchedAt: new Date()
});
AdminDashboardNext.fetchProblems()
.then(model => this.set("problems", model.problems))
.finally(() => this.set("loadingProblems", false));
},
@computed("problemsFetchedAt")
problemsTimestamp(problemsFetchedAt) {
return moment(problemsFetchedAt)
.locale("en")
.format("LLL");
},
actions: {
refreshProblems() {
this._loadProblems();
}
}
});

View File

@ -1,78 +1,90 @@
import AdminDashboard from "admin/models/admin-dashboard"; import { setting } from "discourse/lib/computed";
import Report from "admin/models/report";
import AdminUser from "admin/models/admin-user";
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
import AdminDashboard from "admin/models/admin-dashboard";
import VersionCheck from "admin/models/version-check";
const ATTRIBUTES = [ const PROBLEMS_CHECK_MINUTES = 1;
"admins",
"moderators",
"silenced",
"suspended",
"top_traffic_sources",
"top_referred_topics",
"updated_at"
];
const REPORTS = [
"global_reports",
"page_view_reports",
"private_message_reports",
"http_reports",
"user_reports",
"mobile_reports"
];
// This controller supports the default interface when you enter the admin section.
export default Ember.Controller.extend({ export default Ember.Controller.extend({
loading: null, isLoading: false,
versionCheck: null,
dashboardFetchedAt: null, dashboardFetchedAt: null,
exceptionController: Ember.inject.controller("exception"), exceptionController: Ember.inject.controller("exception"),
showVersionChecks: setting("version_checks"),
@computed("problems.length")
foundProblems(problemsLength) {
return this.currentUser.get("admin") && (problemsLength || 0) > 0;
},
fetchProblems() {
if (this.get("isLoadingProblems")) return;
if (
!this.get("problemsFetchedAt") ||
moment()
.subtract(PROBLEMS_CHECK_MINUTES, "minutes")
.toDate() > this.get("problemsFetchedAt")
) {
this._loadProblems();
}
},
fetchDashboard() { fetchDashboard() {
const versionChecks = this.siteSettings.version_checks;
if (this.get("isLoading") || !versionChecks) return;
if ( if (
!this.get("dashboardFetchedAt") || !this.get("dashboardFetchedAt") ||
moment() moment()
.subtract(30, "minutes") .subtract(30, "minutes")
.toDate() > this.get("dashboardFetchedAt") .toDate() > this.get("dashboardFetchedAt")
) { ) {
this.set("loading", true); this.set("isLoading", true);
AdminDashboard.find()
.then(d => {
this.set("dashboardFetchedAt", new Date());
REPORTS.forEach(name => AdminDashboard.fetch()
this.set(name, d[name].map(r => Report.create(r))) .then(model => {
); let properties = {
dashboardFetchedAt: new Date()
};
const topReferrers = d.top_referrers; if (versionChecks) {
if (topReferrers && topReferrers.data) { properties.versionCheck = VersionCheck.create(model.version_check);
d.top_referrers.data = topReferrers.data.map(user =>
AdminUser.create(user)
);
this.set("top_referrers", topReferrers);
} }
ATTRIBUTES.forEach(a => this.set(a, d[a])); this.setProperties(properties);
}) })
.catch(e => { .catch(e => {
this.get("exceptionController").set("thrown", e.jqXHR); this.get("exceptionController").set("thrown", e.jqXHR);
this.replaceRoute("exception"); this.replaceRoute("exception");
}) })
.finally(() => { .finally(() => {
this.set("loading", false); this.set("isLoading", false);
}); });
} }
}, },
@computed("updated_at") _loadProblems() {
updatedTimestamp(updatedAt) { this.setProperties({
return moment(updatedAt).format("LLL"); loadingProblems: true,
problemsFetchedAt: new Date()
});
AdminDashboard.fetchProblems()
.then(model => this.set("problems", model.problems))
.finally(() => this.set("loadingProblems", false));
},
@computed("problemsFetchedAt")
problemsTimestamp(problemsFetchedAt) {
return moment(problemsFetchedAt)
.locale("en")
.format("LLL");
}, },
actions: { actions: {
showTrafficReport() { refreshProblems() {
this.set("showTrafficReport", true); this._loadProblems();
} }
} }
}); });

View File

@ -15,7 +15,7 @@ export default Ember.Controller.extend({
@computed("application.currentPath") @computed("application.currentPath")
adminContentsClassName(currentPath) { adminContentsClassName(currentPath) {
return currentPath let cssClasses = currentPath
.split(".") .split(".")
.filter(segment => { .filter(segment => {
return ( return (
@ -27,5 +27,12 @@ export default Ember.Controller.extend({
}) })
.map(Ember.String.dasherize) .map(Ember.String.dasherize)
.join(" "); .join(" ");
// this is done to avoid breaking css customizations
if (cssClasses.includes("dashboard")) {
cssClasses = `${cssClasses} dashboard-next`;
}
return cssClasses;
} }
}); });

View File

@ -1,52 +0,0 @@
import { ajax } from "discourse/lib/ajax";
const GENERAL_ATTRIBUTES = ["updated_at"];
const AdminDashboardNext = Discourse.Model.extend({});
AdminDashboardNext.reopenClass({
fetch() {
return ajax("/admin/dashboard.json").then(json => {
const model = AdminDashboardNext.create();
model.set("version_check", json.version_check);
return model;
});
},
fetchGeneral() {
return ajax("/admin/dashboard/general.json").then(json => {
const model = AdminDashboardNext.create();
const attributes = {};
GENERAL_ATTRIBUTES.forEach(a => (attributes[a] = json[a]));
model.setProperties({
reports: json.reports,
attributes,
loaded: true
});
return model;
});
},
/**
Only fetch the list of problems that should be rendered on the dashboard.
The model will only have its "problems" attribute set.
@method fetchProblems
@return {jqXHR} a jQuery Promise object
**/
fetchProblems: function() {
return ajax("/admin/dashboard/problems.json", {
type: "GET",
dataType: "json"
}).then(function(json) {
var model = AdminDashboardNext.create(json);
model.set("loaded", true);
return model;
});
}
});
export default AdminDashboardNext;

View File

@ -1,18 +1,38 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
const GENERAL_ATTRIBUTES = ["updated_at"];
const AdminDashboard = Discourse.Model.extend({}); const AdminDashboard = Discourse.Model.extend({});
AdminDashboard.reopenClass({ AdminDashboard.reopenClass({
/** fetch() {
Fetch all dashboard data. This can be an expensive request when the cached data return ajax("/admin/dashboard.json").then(json => {
has expired and the server must collect the data again. const model = AdminDashboard.create();
model.set("version_check", json.version_check);
return model;
});
},
@method find fetchGeneral() {
@return {jqXHR} a jQuery Promise object return ajax("/admin/dashboard/general.json").then(json => {
**/ const model = AdminDashboard.create();
find: function() {
return ajax("/admin/dashboard-old.json").then(function(json) { const attributes = {};
var model = AdminDashboard.create(json); GENERAL_ATTRIBUTES.forEach(a => (attributes[a] = json[a]));
model.setProperties({
reports: json.reports,
attributes,
loaded: true
});
return model;
});
},
fetchProblems() {
return ajax("/admin/dashboard/problems.json").then(json => {
const model = AdminDashboard.create(json);
model.set("loaded", true); model.set("loaded", true);
return model; return model;
}); });

View File

@ -0,0 +1,5 @@
export default Discourse.Route.extend({
activate() {
this.controllerFor("admin-dashboard-general").fetchDashboard();
}
});

View File

@ -1,5 +0,0 @@
export default Discourse.Route.extend({
activate() {
this.controllerFor("admin-dashboard-next-general").fetchDashboard();
}
});

View File

@ -1,9 +0,0 @@
import { scrollTop } from "discourse/mixins/scroll-top";
export default Discourse.Route.extend({
activate() {
this.controllerFor("admin-dashboard-next").fetchProblems();
this.controllerFor("admin-dashboard-next").fetchDashboard();
scrollTop();
}
});

View File

@ -1,5 +1,9 @@
import { scrollTop } from "discourse/mixins/scroll-top";
export default Discourse.Route.extend({ export default Discourse.Route.extend({
setupController(controller) { activate() {
controller.fetchDashboard(); this.controllerFor("admin-dashboard").fetchProblems();
this.controllerFor("admin-dashboard").fetchDashboard();
scrollTop();
} }
}); });

View File

@ -1,5 +1,5 @@
export default Discourse.Route.extend({ export default Discourse.Route.extend({
beforeModel() { beforeModel() {
this.transitionTo("admin.dashboardNextReports"); this.transitionTo("admin.dashboardReports");
} }
}); });

View File

@ -1,18 +1,16 @@
export default function() { export default function() {
this.route("admin", { resetNamespace: true }, function() { this.route("admin", { resetNamespace: true }, function() {
this.route("dashboard", { path: "/dashboard-old" }); this.route("dashboard", { path: "/" }, function() {
this.route("dashboardNext", { path: "/" }, function() {
this.route("general", { path: "/" }); this.route("general", { path: "/" });
this.route("admin.dashboardNextModeration", { this.route("admin.dashboardModeration", {
path: "/dashboard/moderation", path: "/dashboard/moderation",
resetNamespace: true resetNamespace: true
}); });
this.route("admin.dashboardNextSecurity", { this.route("admin.dashboardSecurity", {
path: "/dashboard/security", path: "/dashboard/security",
resetNamespace: true resetNamespace: true
}); });
this.route("admin.dashboardNextReports", { this.route("admin.dashboardReports", {
path: "/dashboard/reports", path: "/dashboard/reports",
resetNamespace: true resetNamespace: true
}); });

View File

@ -1,11 +1,11 @@
{{#admin-wrapper class="container"}} {{#admin-wrapper class="container"}}
<div class="row"> <div class="row">
<div class="full-width"> <div class="full-width">
<div class="admin-main-nav"> <div class="admin-main-nav">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
{{nav-item route='admin.dashboardNext' label='admin.dashboard.title'}} {{nav-item route='admin.dashboard' label='admin.dashboard.title'}}
{{#if currentUser.admin}} {{#if currentUser.admin}}
{{nav-item route='adminSiteSettings' label='admin.site_settings.title'}} {{nav-item route='adminSiteSettings' label='admin.site_settings.title'}}
{{/if}} {{/if}}

View File

@ -6,7 +6,7 @@
<ul class="breadcrumb"> <ul class="breadcrumb">
{{#if showAllReportsLink}} {{#if showAllReportsLink}}
<li class="item all-reports"> <li class="item all-reports">
{{#link-to "admin.dashboardNextReports" class="report-url"}} {{#link-to "admin.dashboardReports" class="report-url"}}
{{i18n "admin.dashboard.all_reports"}} {{i18n "admin.dashboard.all_reports"}}
{{/link-to}} {{/link-to}}
</li> </li>

View File

@ -1,227 +1,38 @@
{{#conditional-loading-spinner condition=loading}} {{plugin-outlet name="admin-dashboard-top"}}
<div class="alert alert-info">
The old dashboard is going to be removed in Discourse 2.2
</div>
<div class="dashboard-left">
<div class="dashboard-stats trust-levels">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>&nbsp;</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
</tr>
</thead>
<tbody>
{{#each user_reports as |r|}}
{{admin-report-trust-level-counts report=r}}
{{/each}}
</tbody>
</table>
</div>
<div class="dashboard-stats totals"> {{#if showVersionChecks}}
<table> <div class="section-top">
<tr> <div class="version-checks">
<td class="title">{{d-icon "shield-alt"}} {{i18n 'admin.dashboard.admins'}}</td> {{partial "admin/templates/version-checks"}}
<td class="value">{{#link-to 'adminUsersList.show' 'admins'}}{{admins}}{{/link-to}}</td>
<td class="title">{{d-icon "ban"}} {{i18n 'admin.dashboard.suspended'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'suspended'}}{{suspended}}{{/link-to}}</td>
</tr>
<tr>
<td class="title">{{d-icon "shield-alt"}} {{i18n 'admin.dashboard.moderators'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'moderators'}}{{moderators}}{{/link-to}}</td>
<td class="title">{{d-icon "ban"}} {{i18n 'admin.dashboard.silenced'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'silenced'}}{{silenced}}{{/link-to}}</td>
</tr>
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#each global_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
</tbody>
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.page_views'}}">{{i18n 'admin.dashboard.page_views_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#each page_view_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
</tbody>
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.private_messages_title'}}">{{d-icon "envelope"}} {{i18n 'admin.dashboard.private_messages_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#each private_message_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
</tbody>
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.mobile_title'}}">{{i18n 'admin.dashboard.mobile_title'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#each mobile_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
</tbody>
</table>
</div>
{{#if showTrafficReport}}
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.traffic'}}">{{i18n 'admin.dashboard.traffic_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
<tbody>
{{#unless loading}}
{{#each http_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
{{/unless}}
</tbody>
</table>
</div>
{{else}}
<div class="dashboard-stats">
<a href {{action 'showTrafficReport'}}>{{i18n 'admin.dashboard.show_traffic_report'}}</a>
</div>
{{/if}}
</div>
<div class="dashboard-right">
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title">{{top_referred_topics.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
<th>{{top_referred_topics.ytitles.num_clicks}}</th>
</tr>
</thead>
{{#each top_referred_topics.data as |data|}}
<tbody>
<tr>
<td class="title">
<div class="referred-topic-title">
<div class="overflow-ellipsis">
<a href="{{unbound data.topic_url}}">{{data.topic_title}}</a>
</div>
</div>
</td>
<td class="value">{{number data.num_clicks}}</td>
</tr>
</tbody>
{{/each}}
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title">{{top_traffic_sources.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
<th>{{top_traffic_sources.ytitles.num_clicks}}</th>
<th>{{top_traffic_sources.ytitles.num_topics}}</th>
</tr>
</thead>
{{#each top_traffic_sources.data as |s|}}
<tbody>
<tr>
<td class="title">{{s.domain}}</td>
<td class="value">{{number s.num_clicks}}</td>
<td class="value">{{number s.num_topics}}</td>
</tr>
</tbody>
{{/each}}
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title">{{top_referrers.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
<th>{{top_referrers.ytitles.num_clicks}}</th>
<th>{{top_referrers.ytitles.num_topics}}</th>
</tr>
</thead>
{{#each top_referrers.data as |r|}}
<tbody>
<tr>
<td class="title">{{#link-to 'adminUser' r.user_id r.username}}{{unbound r.username}}{{/link-to}}</td>
<td class="value">{{number r.num_clicks}}</td>
<td class="value">{{number r.num_topics}}</td>
</tr>
</tbody>
{{/each}}
</table>
</div> </div>
</div> </div>
<div class='clearfix'></div> {{/if}}
<div class="dashboard-stats pull-right"> {{partial "admin/templates/dashboard-problems"}}
<div class="pull-right">{{i18n 'admin.dashboard.last_updated'}} {{updatedTimestamp}}</div>
<div class='clearfix'></div> <ul class="navigation">
</div> <li class="navigation-item general">
<div class='clearfix'></div> {{#link-to "admin.dashboard.general" class="navigation-link"}}
{{/conditional-loading-spinner}} {{i18n "admin.dashboard.general_tab"}}
{{/link-to}}
</li>
<li class="navigation-item moderation">
{{#link-to "admin.dashboardModeration" class="navigation-link"}}
{{i18n "admin.dashboard.moderation_tab"}}
{{/link-to}}
</li>
<li class="navigation-item security">
{{#link-to "admin.dashboardSecurity" class="navigation-link"}}
{{i18n "admin.dashboard.security_tab"}}
{{/link-to}}
</li>
<li class="navigation-item reports">
{{#link-to "admin.dashboardReports" class="navigation-link"}}
{{i18n "admin.dashboard.reports_tab"}}
{{/link-to}}
</li>
</ul>
{{outlet}}
{{plugin-outlet name="admin-dashboard-bottom"}}

View File

@ -1,38 +0,0 @@
{{plugin-outlet name="admin-dashboard-top"}}
{{#if showVersionChecks}}
<div class="section-top">
<div class="version-checks">
{{partial "admin/templates/version-checks"}}
</div>
</div>
{{/if}}
{{partial "admin/templates/dashboard-problems"}}
<ul class="navigation">
<li class="navigation-item general">
{{#link-to "admin.dashboardNext.general" class="navigation-link"}}
{{i18n "admin.dashboard.general_tab"}}
{{/link-to}}
</li>
<li class="navigation-item moderation">
{{#link-to "admin.dashboardNextModeration" class="navigation-link"}}
{{i18n "admin.dashboard.moderation_tab"}}
{{/link-to}}
</li>
<li class="navigation-item security">
{{#link-to "admin.dashboardNextSecurity" class="navigation-link"}}
{{i18n "admin.dashboard.security_tab"}}
{{/link-to}}
</li>
<li class="navigation-item reports">
{{#link-to "admin.dashboardNextReports" class="navigation-link"}}
{{i18n "admin.dashboard.reports_tab"}}
{{/link-to}}
</li>
</ul>
{{outlet}}
{{plugin-outlet name="admin-dashboard-bottom"}}

View File

@ -961,7 +961,7 @@ table#user-badges {
} }
// Styles for subtabs in admin // Styles for subtabs in admin
@import "common/admin/dashboard_next"; @import "common/admin/dashboard";
@import "common/admin/settings"; @import "common/admin/settings";
@import "common/admin/users"; @import "common/admin/users";
@import "common/admin/suspend"; @import "common/admin/suspend";
@ -980,4 +980,3 @@ table#user-badges {
@import "common/admin/admin_report_stacked_chart"; @import "common/admin/admin_report_stacked_chart";
@import "common/admin/admin_report_table"; @import "common/admin/admin_report_table";
@import "common/admin/admin_report_inline_table"; @import "common/admin/admin_report_inline_table";
@import "common/admin/dashboard_previous";

View File

@ -1,10 +1,12 @@
.admin-reports, .admin-reports,
.dashboard,
.dashboard-next { .dashboard-next {
&.admin-contents { &.admin-contents {
margin: 10px 0 0 0; margin: 10px 0 0 0;
} }
} }
.dashboard,
.dashboard-next { .dashboard-next {
.section-top { .section-top {
margin-bottom: 0.5em; margin-bottom: 0.5em;
@ -35,15 +37,15 @@
} }
} }
&.dashboard-next-moderation .navigation-item.moderation { &.dashboard-moderation .navigation-item.moderation {
@include active-navigation-item; @include active-navigation-item;
} }
&.dashboard-next-security .navigation-item.security { &.dashboard-security .navigation-item.security {
@include active-navigation-item; @include active-navigation-item;
} }
&.dashboard-next-reports .navigation-item.reports { &.dashboard-reports .navigation-item.reports {
@include active-navigation-item; @include active-navigation-item;
} }
@ -508,8 +510,8 @@
margin-bottom: 1.5em; margin-bottom: 1.5em;
} }
.dashboard-next-moderation, .dashboard-moderation,
.dashboard-next-security { .dashboard-security {
.section-body { .section-body {
margin-bottom: 1em; margin-bottom: 1em;
} }
@ -538,7 +540,7 @@
} }
} }
.dashboard-next-moderation { .dashboard-moderation {
.admin-dashboard-moderation-top { .admin-dashboard-moderation-top {
display: grid; display: grid;
grid-template-columns: repeat(12, 1fr); grid-template-columns: repeat(12, 1fr);
@ -547,7 +549,7 @@
} }
} }
.dashboard-next-reports { .dashboard-reports {
.reports-list { .reports-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -576,3 +578,96 @@
} }
} }
} }
.version-checks {
display: flex;
flex-wrap: wrap;
.section-title {
flex: 1 1 100%;
border-bottom: 1px solid $primary-low;
margin-bottom: 0.5em;
}
}
.version-check {
display: flex;
flex: 1 1 50%;
flex-wrap: wrap;
align-items: flex-start;
align-self: flex-start;
justify-content: space-between;
padding: 10px 0 10px 0;
.upgrade-header {
flex: 1 1 100%;
@media screen and (max-width: 650px) {
margin: 0;
}
tr {
border: none;
}
th {
background: transparent;
padding: 0;
}
}
h2 {
flex: 1 1 100%;
}
.version-number {
font-size: $font-up-2;
line-height: $line-height-medium;
box-sizing: border-box;
font-weight: bold;
margin: 0 0 1em 0;
padding-right: 20px;
flex: 1 1 27%;
h3 {
flex: 1 0 auto;
white-space: nowrap;
}
h4 {
font-size: $font-down-2;
margin-bottom: 0;
}
}
.version-status {
display: flex;
align-items: center;
margin: 0 0 1em 0;
flex: 1 1 24%;
box-sizing: border-box;
padding-right: 20px;
min-width: 250px;
@include breakpoint(medium) {
max-width: unset;
}
.face {
margin: 0 0.75em 0 0;
font-size: $font-up-3;
}
}
&.critical .version-notes .normal-note {
display: none;
}
&.normal .version-notes .critical-note {
display: none;
}
.fa {
font-size: $font-up-4;
}
.up-to-date {
color: $success;
}
.updates-available {
color: $danger;
}
.critical-updates-available {
color: $danger;
}
}
.update-nag {
.d-icon {
font-size: $font-up-3;
}
}

View File

@ -1,268 +0,0 @@
// Styles for admin/dashboard-old
.dashboard-left {
float: left;
width: 60%;
}
.dashboard-right {
float: right;
width: 40%;
.dashboard-stats {
width: 100%;
margin-left: 0;
}
}
.version-checks {
display: flex;
flex-wrap: wrap;
.section-title {
flex: 1 1 100%;
border-bottom: 1px solid $primary-low;
margin-bottom: 0.5em;
}
}
.version-check {
display: flex;
flex: 1 1 50%;
flex-wrap: wrap;
align-items: flex-start;
align-self: flex-start;
justify-content: space-between;
padding: 10px 0 10px 0;
.upgrade-header {
flex: 1 1 100%;
@media screen and (max-width: 650px) {
margin: 0;
}
tr {
border: none;
}
th {
background: transparent;
padding: 0;
}
}
h2 {
flex: 1 1 100%;
}
.version-number {
font-size: $font-up-2;
line-height: $line-height-medium;
box-sizing: border-box;
font-weight: bold;
margin: 0 0 1em 0;
padding-right: 20px;
flex: 1 1 27%;
h3 {
flex: 1 0 auto;
white-space: nowrap;
}
h4 {
font-size: $font-down-2;
margin-bottom: 0;
}
}
.version-status {
display: flex;
align-items: center;
margin: 0 0 1em 0;
flex: 1 1 24%;
box-sizing: border-box;
padding-right: 20px;
min-width: 250px;
@include breakpoint(medium) {
max-width: unset;
}
.face {
margin: 0 0.75em 0 0;
font-size: $font-up-3;
}
}
&.critical .version-notes .normal-note {
display: none;
}
&.normal .version-notes .critical-note {
display: none;
}
.fa {
font-size: $font-up-4;
}
.up-to-date {
color: $success;
}
.updates-available {
color: $danger;
}
.critical-updates-available {
color: $danger;
}
}
.update-nag {
.d-icon {
font-size: $font-up-3;
}
}
.dashboard-stats {
box-sizing: border-box;
margin-bottom: 30px;
flex: 1 1 50%;
box-sizing: border-box;
&.version-check {
margin: 0;
}
&.detected-problems {
border-left: 1px solid $primary-low;
margin: 10px 0 0 0;
padding-left: 20px;
}
h4 {
font-weight: normal;
margin-bottom: 8px;
}
@media screen and (max-width: 650px) {
flex: 1 1 100%;
}
table {
width: 100%;
.title {
.d-icon {
color: $primary;
}
.d-icon-heart {
color: $love;
}
}
th {
text-align: center;
background: $primary-low;
}
th.title {
text-align: left;
}
thead {
tr:hover > td {
background-color: $secondary;
}
}
td.value {
font-weight: bold;
text-align: center;
.d-icon {
display: none;
}
&.high-trending-up,
&.trending-up {
.up {
color: $success;
display: inline;
}
}
&.high-trending-down,
&.trending-down {
.down {
color: $danger;
display: inline;
}
}
&.no-change {
.down {
display: inline;
visibility: hidden;
}
}
}
tr.reverse-colors {
td.value.high-trending-down .down,
td.value.trending-down .down {
color: $success;
}
td.value.high-trending-up .up,
td.value.trending-up .up {
color: $danger;
}
}
}
&.detected-problems {
display: flex;
margin-bottom: 30px;
.look-here {
margin: 10px 20px;
.fa {
font-size: $font-up-5;
color: $danger;
}
}
@media screen and (max-width: 650px) {
border-left: none;
border-top: 1px solid $primary-low;
padding: 20px 0 0 0;
.look-here {
margin-left: 0;
}
}
h3 {
display: flex;
}
.problem-messages {
display: flex;
a {
text-decoration: underline;
}
.btn {
background: $primary-low;
}
ul {
margin-left: 0;
padding-left: 90px;
@media screen and (max-width: 650px) {
padding-left: 20px;
}
li {
margin-bottom: 10px;
}
}
p.actions {
padding-left: 75px;
@media screen and (max-width: 650px) {
padding-left: 0;
}
}
}
}
&.totals {
table {
width: auto;
}
margin-top: 12px;
padding-left: 5px;
.value {
text-align: left;
font-weight: bold;
padding-left: 8px;
padding-right: 30px;
}
}
&.trust-levels {
margin-bottom: 0;
table {
margin-bottom: 0;
}
td.value {
width: 45px;
}
}
.referred-topic-title {
width: 355px;
@media all and (min-width: 1000px) and (max-width: 1139px) {
width: 305px;
}
@include breakpoint(medium) {
width: 265px;
}
}
}

View File

@ -22,7 +22,7 @@
@import "mobile/emoji"; @import "mobile/emoji";
@import "mobile/ring"; @import "mobile/ring";
@import "mobile/group"; @import "mobile/group";
@import "mobile/dashboard_next"; @import "mobile/dashboard";
@import "mobile/admin_customize"; @import "mobile/admin_customize";
@import "mobile/admin_reports"; @import "mobile/admin_reports";
@import "mobile/admin_report"; @import "mobile/admin_report";

View File

@ -1,3 +1,4 @@
.dashboard,
.dashboard-next { .dashboard-next {
.activity-metrics .counters-list { .activity-metrics .counters-list {
font-size: $font-down-1; font-size: $font-down-1;

View File

@ -1,8 +1,20 @@
class Admin::DashboardController < Admin::AdminController class Admin::DashboardController < Admin::AdminController
def index def index
dashboard_data = AdminDashboardData.fetch_cached_stats || Jobs::DashboardStats.new.execute({}) data = AdminDashboardIndexData.fetch_cached_stats
dashboard_data.merge!(version_check: DiscourseUpdates.check_version.as_json) if SiteSetting.version_checks?
render json: dashboard_data if SiteSetting.version_checks?
data.merge!(version_check: DiscourseUpdates.check_version.as_json)
end
render json: data
end
def moderation; end
def security; end
def reports; end
def general
render json: AdminDashboardGeneralData.fetch_cached_stats
end end
def problems def problems

View File

@ -1,19 +0,0 @@
class Admin::DashboardNextController < Admin::AdminController
def index
data = AdminDashboardNextIndexData.fetch_cached_stats
if SiteSetting.version_checks?
data.merge!(version_check: DiscourseUpdates.check_version.as_json)
end
render json: data
end
def moderation; end
def security; end
def reports; end
def general
render json: AdminDashboardNextGeneralData.fetch_cached_stats
end
end

View File

@ -3,36 +3,29 @@ require_dependency 'mem_info'
class AdminDashboardData class AdminDashboardData
include StatsCacheable include StatsCacheable
GLOBAL_REPORTS ||= [ def initialize(opts = {})
'visits', @opts = opts
'signups', end
'profile_views',
'topics',
'posts',
'time_to_first_response',
'topics_with_no_response',
'likes',
'flags',
'bookmarks',
'emails',
]
PAGE_VIEW_REPORTS ||= ['page_view_total_reqs'] + ApplicationRequest.req_types.keys.select { |r| r =~ /^page_view_/ && r !~ /mobile/ }.map { |r| r + "_reqs" } def self.fetch_stats
new.as_json
end
PRIVATE_MESSAGE_REPORTS ||= [ def get_json
'user_to_user_private_messages', {}
'user_to_user_private_messages_with_replies', end
'system_private_messages',
'notify_moderators_private_messages',
'notify_user_private_messages',
'moderator_warning_private_messages',
]
HTTP_REPORTS ||= ApplicationRequest.req_types.keys.select { |r| r =~ /^http_/ }.map { |r| r + "_reqs" }.sort def as_json(_options = nil)
@json ||= get_json
end
USER_REPORTS ||= ['users_by_trust_level'] def self.reports(source)
source.map { |type| Report.find(type).as_json }
end
MOBILE_REPORTS ||= ['mobile_visits'] + ApplicationRequest.req_types.keys.select { |r| r =~ /mobile/ }.map { |r| r + "_reqs" } def self.stats_cache_key
"dashboard-data-#{Report::SCHEMA_VERSION}"
end
def self.add_problem_check(*syms, &blk) def self.add_problem_check(*syms, &blk)
@problem_syms.push(*syms) if syms @problem_syms.push(*syms) if syms
@ -40,10 +33,6 @@ class AdminDashboardData
end end
class << self; attr_reader :problem_syms, :problem_blocks, :problem_messages; end class << self; attr_reader :problem_syms, :problem_blocks, :problem_messages; end
def initialize(opts = {})
@opts = opts
end
def problems def problems
problems = [] problems = []
AdminDashboardData.problem_syms.each do |sym| AdminDashboardData.problem_syms.each do |sym|
@ -67,7 +56,7 @@ class AdminDashboardData
end end
def self.problems_started_key def self.problems_started_key
"dash-problems-started-at" 'dash-problems-started-at'
end end
def self.set_problems_started def self.set_problems_started
@ -110,14 +99,6 @@ class AdminDashboardData
end end
reset_problem_checks reset_problem_checks
def self.fetch_stats
AdminDashboardData.new.as_json
end
def self.stats_cache_key
'dash-stats'
end
def self.fetch_problems(opts = {}) def self.fetch_problems(opts = {})
AdminDashboardData.new(opts).problems AdminDashboardData.new(opts).problems
end end
@ -142,29 +123,6 @@ class AdminDashboardData
"admin-problem:#{i18n_key}" "admin-problem:#{i18n_key}"
end end
def as_json(_options = nil)
@json ||= {
global_reports: AdminDashboardData.reports(GLOBAL_REPORTS),
page_view_reports: AdminDashboardData.reports(PAGE_VIEW_REPORTS),
private_message_reports: AdminDashboardData.reports(PRIVATE_MESSAGE_REPORTS),
http_reports: AdminDashboardData.reports(HTTP_REPORTS),
user_reports: AdminDashboardData.reports(USER_REPORTS),
mobile_reports: AdminDashboardData.reports(MOBILE_REPORTS),
admins: User.admins.count,
moderators: User.moderators.count,
suspended: User.suspended.count,
silenced: User.silenced.count,
top_referrers: IncomingLinksReport.find('top_referrers').as_json,
top_traffic_sources: IncomingLinksReport.find('top_traffic_sources').as_json,
top_referred_topics: IncomingLinksReport.find('top_referred_topics').as_json,
updated_at: Time.zone.now.as_json
}
end
def self.reports(source)
source.map { |type| Report.find(type).as_json }
end
def rails_env_check def rails_env_check
I18n.t("dashboard.rails_env_warning", env: Rails.env) unless Rails.env.production? I18n.t("dashboard.rails_env_warning", env: Rails.env) unless Rails.env.production?
end end
@ -260,7 +218,7 @@ class AdminDashboardData
def missing_mailgun_api_key def missing_mailgun_api_key
return unless SiteSetting.reply_by_email_enabled return unless SiteSetting.reply_by_email_enabled
return unless ActionMailer::Base.smtp_settings[:address]["smtp.mailgun.org"] return unless ActionMailer::Base.smtp_settings[:address]['smtp.mailgun.org']
return unless SiteSetting.mailgun_api_key.blank? return unless SiteSetting.mailgun_api_key.blank?
I18n.t('dashboard.missing_mailgun_api_key') I18n.t('dashboard.missing_mailgun_api_key')
end end
@ -274,14 +232,14 @@ class AdminDashboardData
old_themes = RemoteTheme.out_of_date_themes old_themes = RemoteTheme.out_of_date_themes
return unless old_themes.present? return unless old_themes.present?
themes_html_format(old_themes, "dashboard.out_of_date_themes") themes_html_format(old_themes, 'dashboard.out_of_date_themes')
end end
def unreachable_themes def unreachable_themes
themes = RemoteTheme.unreachable_themes themes = RemoteTheme.unreachable_themes
return unless themes.present? return unless themes.present?
themes_html_format(themes, "dashboard.unreachable_themes") themes_html_format(themes, 'dashboard.unreachable_themes')
end end
private private
@ -291,8 +249,6 @@ class AdminDashboardData
"<li><a href=\"/admin/customize/themes/#{id}\">#{CGI.escapeHTML(name)}</a></li>" "<li><a href=\"/admin/customize/themes/#{id}\">#{CGI.escapeHTML(name)}</a></li>"
end.join("\n") end.join("\n")
message = I18n.t(i18n_key) "#{I18n.t(i18n_key)}<ul>#{html}</ul>"
message += "<ul>#{html}</ul>"
message
end end
end end

View File

@ -1,4 +1,4 @@
class AdminDashboardNextGeneralData < AdminDashboardNextData class AdminDashboardGeneralData < AdminDashboardData
def get_json def get_json
{ {
updated_at: Time.zone.now.as_json updated_at: Time.zone.now.as_json

View File

@ -1,4 +1,4 @@
class AdminDashboardNextIndexData < AdminDashboardNextData class AdminDashboardIndexData < AdminDashboardData
def get_json def get_json
{ {
updated_at: Time.zone.now.as_json updated_at: Time.zone.now.as_json

View File

@ -1,27 +0,0 @@
class AdminDashboardNextData
include StatsCacheable
def initialize(opts = {})
@opts = opts
end
def self.fetch_stats
new.as_json
end
def get_json
{}
end
def as_json(_options = nil)
@json ||= get_json
end
def self.reports(source)
source.map { |type| Report.find(type).as_json }
end
def self.stats_cache_key
"dashboard-next-data-#{Report::SCHEMA_VERSION}"
end
end

View File

@ -240,13 +240,11 @@ Discourse::Application.routes.draw do
get "version_check" => "versions#show" get "version_check" => "versions#show"
get "dashboard" => "dashboard_next#index" get "dashboard" => "dashboard#index"
get "dashboard/general" => "dashboard_next#general" get "dashboard/general" => "dashboard#general"
get "dashboard/moderation" => "dashboard_next#moderation" get "dashboard/moderation" => "dashboard#moderation"
get "dashboard/security" => "dashboard_next#security" get "dashboard/security" => "dashboard#security"
get "dashboard/reports" => "dashboard_next#reports" get "dashboard/reports" => "dashboard#reports"
get "dashboard-old" => "dashboard#index"
resources :dashboard, only: [:index] do resources :dashboard, only: [:index] do
collection do collection do

View File

@ -1,6 +1,6 @@
import { acceptance } from "helpers/qunit-helpers"; import { acceptance } from "helpers/qunit-helpers";
acceptance("Dashboard Next", { acceptance("Dashboard", {
loggedIn: true, loggedIn: true,
settings: { settings: {
dashboard_general_tab_activity_metrics: "page_view_total_reqs" dashboard_general_tab_activity_metrics: "page_view_total_reqs"
@ -9,22 +9,16 @@ acceptance("Dashboard Next", {
QUnit.test("Dashboard", async assert => { QUnit.test("Dashboard", async assert => {
await visit("/admin"); await visit("/admin");
assert.ok(exists(".dashboard-next"), "has dashboard-next class"); assert.ok(exists(".dashboard"), "has dashboard-next class");
}); });
QUnit.test("tabs", async assert => { QUnit.test("tabs", async assert => {
await visit("/admin"); await visit("/admin");
assert.ok(exists(".dashboard-next .navigation-item.general"), "general tab"); assert.ok(exists(".dashboard .navigation-item.general"), "general tab");
assert.ok( assert.ok(exists(".dashboard .navigation-item.moderation"), "moderation tab");
exists(".dashboard-next .navigation-item.moderation"), assert.ok(exists(".dashboard .navigation-item.security"), "security tab");
"moderation tab" assert.ok(exists(".dashboard .navigation-item.reports"), "reports tab");
);
assert.ok(
exists(".dashboard-next .navigation-item.security"),
"security tab"
);
assert.ok(exists(".dashboard-next .navigation-item.reports"), "reports tab");
}); });
QUnit.test("general tab", async assert => { QUnit.test("general tab", async assert => {
@ -61,33 +55,33 @@ QUnit.test("general tab - activity metrics", async assert => {
QUnit.test("reports tab", async assert => { QUnit.test("reports tab", async assert => {
await visit("/admin"); await visit("/admin");
await click(".dashboard-next .navigation-item.reports .navigation-link"); await click(".dashboard .navigation-item.reports .navigation-link");
assert.equal( assert.equal(
find(".dashboard-next .reports-index.section .reports-list .report").length, find(".dashboard .reports-index.section .reports-list .report").length,
1 1
); );
await fillIn(".dashboard-next .filter-reports-input", "flags"); await fillIn(".dashboard .filter-reports-input", "flags");
assert.equal( assert.equal(
find(".dashboard-next .reports-index.section .reports-list .report").length, find(".dashboard .reports-index.section .reports-list .report").length,
0 0
); );
await click(".dashboard-next .navigation-item.security .navigation-link"); await click(".dashboard .navigation-item.security .navigation-link");
await click(".dashboard-next .navigation-item.reports .navigation-link"); await click(".dashboard .navigation-item.reports .navigation-link");
assert.equal( assert.equal(
find(".dashboard-next .reports-index.section .reports-list .report").length, find(".dashboard .reports-index.section .reports-list .report").length,
1, 1,
"navigating back and forth resets filter" "navigating back and forth resets filter"
); );
await fillIn(".dashboard-next .filter-reports-input", "activities"); await fillIn(".dashboard .filter-reports-input", "activities");
assert.equal( assert.equal(
find(".dashboard-next .reports-index.section .reports-list .report").length, find(".dashboard .reports-index.section .reports-list .report").length,
1, 1,
"filter is case insensitive" "filter is case insensitive"
); );