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 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 PeriodComputationMixin from "admin/mixins/period-computation";
@ -88,12 +88,12 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
) {
this.set("isLoading", true);
AdminDashboardNext.fetchGeneral()
.then(adminDashboardNextModel => {
AdminDashboard.fetchGeneral()
.then(adminDashboardModel => {
this.setProperties({
dashboardFetchedAt: new Date(),
model: adminDashboardNextModel,
reports: Ember.makeArray(adminDashboardNextModel.reports).map(x =>
model: adminDashboardModel,
reports: Ember.makeArray(adminDashboardModel.reports).map(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 Report from "admin/models/report";
import AdminUser from "admin/models/admin-user";
import { setting } from "discourse/lib/computed";
import computed from "ember-addons/ember-computed-decorators";
import AdminDashboard from "admin/models/admin-dashboard";
import VersionCheck from "admin/models/version-check";
const ATTRIBUTES = [
"admins",
"moderators",
"silenced",
"suspended",
"top_traffic_sources",
"top_referred_topics",
"updated_at"
];
const PROBLEMS_CHECK_MINUTES = 1;
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({
loading: null,
versionCheck: null,
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("loading", true);
AdminDashboard.find()
.then(d => {
this.set("dashboardFetchedAt", new Date());
this.set("isLoading", true);
REPORTS.forEach(name =>
this.set(name, d[name].map(r => Report.create(r)))
);
AdminDashboard.fetch()
.then(model => {
let properties = {
dashboardFetchedAt: new Date()
};
const topReferrers = d.top_referrers;
if (topReferrers && topReferrers.data) {
d.top_referrers.data = topReferrers.data.map(user =>
AdminUser.create(user)
);
this.set("top_referrers", topReferrers);
if (versionChecks) {
properties.versionCheck = VersionCheck.create(model.version_check);
}
ATTRIBUTES.forEach(a => this.set(a, d[a]));
this.setProperties(properties);
})
.catch(e => {
this.get("exceptionController").set("thrown", e.jqXHR);
this.replaceRoute("exception");
})
.finally(() => {
this.set("loading", false);
this.set("isLoading", false);
});
}
},
@computed("updated_at")
updatedTimestamp(updatedAt) {
return moment(updatedAt).format("LLL");
_loadProblems() {
this.setProperties({
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: {
showTrafficReport() {
this.set("showTrafficReport", true);
refreshProblems() {
this._loadProblems();
}
}
});

View File

@ -15,7 +15,7 @@ export default Ember.Controller.extend({
@computed("application.currentPath")
adminContentsClassName(currentPath) {
return currentPath
let cssClasses = currentPath
.split(".")
.filter(segment => {
return (
@ -27,5 +27,12 @@ export default Ember.Controller.extend({
})
.map(Ember.String.dasherize)
.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";
const GENERAL_ATTRIBUTES = ["updated_at"];
const AdminDashboard = Discourse.Model.extend({});
AdminDashboard.reopenClass({
/**
Fetch all dashboard data. This can be an expensive request when the cached data
has expired and the server must collect the data again.
fetch() {
return ajax("/admin/dashboard.json").then(json => {
const model = AdminDashboard.create();
model.set("version_check", json.version_check);
return model;
});
},
@method find
@return {jqXHR} a jQuery Promise object
**/
find: function() {
return ajax("/admin/dashboard-old.json").then(function(json) {
var model = AdminDashboard.create(json);
fetchGeneral() {
return ajax("/admin/dashboard/general.json").then(json => {
const model = AdminDashboard.create();
const attributes = {};
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);
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({
setupController(controller) {
controller.fetchDashboard();
activate() {
this.controllerFor("admin-dashboard").fetchProblems();
this.controllerFor("admin-dashboard").fetchDashboard();
scrollTop();
}
});

View File

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

View File

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

View File

@ -1,11 +1,11 @@
{{#admin-wrapper class="container"}}
<div class="row">
<div class="full-width">
<div class="full-width">
<div class="admin-main-nav">
<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}}
{{nav-item route='adminSiteSettings' label='admin.site_settings.title'}}
{{/if}}

View File

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

View File

@ -1,227 +1,38 @@
{{#conditional-loading-spinner condition=loading}}
<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>
{{plugin-outlet name="admin-dashboard-top"}}
<div class="dashboard-stats totals">
<table>
<tr>
<td class="title">{{d-icon "shield-alt"}} {{i18n 'admin.dashboard.admins'}}</td>
<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>
{{#if showVersionChecks}}
<div class="section-top">
<div class="version-checks">
{{partial "admin/templates/version-checks"}}
</div>
</div>
<div class='clearfix'></div>
{{/if}}
<div class="dashboard-stats pull-right">
<div class="pull-right">{{i18n 'admin.dashboard.last_updated'}} {{updatedTimestamp}}</div>
<div class='clearfix'></div>
</div>
<div class='clearfix'></div>
{{/conditional-loading-spinner}}
{{partial "admin/templates/dashboard-problems"}}
<ul class="navigation">
<li class="navigation-item general">
{{#link-to "admin.dashboard.general" class="navigation-link"}}
{{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
@import "common/admin/dashboard_next";
@import "common/admin/dashboard";
@import "common/admin/settings";
@import "common/admin/users";
@import "common/admin/suspend";
@ -980,4 +980,3 @@ table#user-badges {
@import "common/admin/admin_report_stacked_chart";
@import "common/admin/admin_report_table";
@import "common/admin/admin_report_inline_table";
@import "common/admin/dashboard_previous";

View File

@ -1,10 +1,12 @@
.admin-reports,
.dashboard,
.dashboard-next {
&.admin-contents {
margin: 10px 0 0 0;
}
}
.dashboard,
.dashboard-next {
.section-top {
margin-bottom: 0.5em;
@ -35,15 +37,15 @@
}
}
&.dashboard-next-moderation .navigation-item.moderation {
&.dashboard-moderation .navigation-item.moderation {
@include active-navigation-item;
}
&.dashboard-next-security .navigation-item.security {
&.dashboard-security .navigation-item.security {
@include active-navigation-item;
}
&.dashboard-next-reports .navigation-item.reports {
&.dashboard-reports .navigation-item.reports {
@include active-navigation-item;
}
@ -508,8 +510,8 @@
margin-bottom: 1.5em;
}
.dashboard-next-moderation,
.dashboard-next-security {
.dashboard-moderation,
.dashboard-security {
.section-body {
margin-bottom: 1em;
}
@ -538,7 +540,7 @@
}
}
.dashboard-next-moderation {
.dashboard-moderation {
.admin-dashboard-moderation-top {
display: grid;
grid-template-columns: repeat(12, 1fr);
@ -547,7 +549,7 @@
}
}
.dashboard-next-reports {
.dashboard-reports {
.reports-list {
display: flex;
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/ring";
@import "mobile/group";
@import "mobile/dashboard_next";
@import "mobile/dashboard";
@import "mobile/admin_customize";
@import "mobile/admin_reports";
@import "mobile/admin_report";

View File

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

View File

@ -1,8 +1,20 @@
class Admin::DashboardController < Admin::AdminController
def index
dashboard_data = AdminDashboardData.fetch_cached_stats || Jobs::DashboardStats.new.execute({})
dashboard_data.merge!(version_check: DiscourseUpdates.check_version.as_json) if SiteSetting.version_checks?
render json: dashboard_data
data = AdminDashboardIndexData.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: AdminDashboardGeneralData.fetch_cached_stats
end
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
include StatsCacheable
GLOBAL_REPORTS ||= [
'visits',
'signups',
'profile_views',
'topics',
'posts',
'time_to_first_response',
'topics_with_no_response',
'likes',
'flags',
'bookmarks',
'emails',
]
def initialize(opts = {})
@opts = opts
end
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 ||= [
'user_to_user_private_messages',
'user_to_user_private_messages_with_replies',
'system_private_messages',
'notify_moderators_private_messages',
'notify_user_private_messages',
'moderator_warning_private_messages',
]
def get_json
{}
end
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)
@problem_syms.push(*syms) if syms
@ -40,10 +33,6 @@ class AdminDashboardData
end
class << self; attr_reader :problem_syms, :problem_blocks, :problem_messages; end
def initialize(opts = {})
@opts = opts
end
def problems
problems = []
AdminDashboardData.problem_syms.each do |sym|
@ -67,7 +56,7 @@ class AdminDashboardData
end
def self.problems_started_key
"dash-problems-started-at"
'dash-problems-started-at'
end
def self.set_problems_started
@ -110,14 +99,6 @@ class AdminDashboardData
end
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 = {})
AdminDashboardData.new(opts).problems
end
@ -142,29 +123,6 @@ class AdminDashboardData
"admin-problem:#{i18n_key}"
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
I18n.t("dashboard.rails_env_warning", env: Rails.env) unless Rails.env.production?
end
@ -260,7 +218,7 @@ class AdminDashboardData
def missing_mailgun_api_key
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?
I18n.t('dashboard.missing_mailgun_api_key')
end
@ -274,14 +232,14 @@ class AdminDashboardData
old_themes = RemoteTheme.out_of_date_themes
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
def unreachable_themes
themes = RemoteTheme.unreachable_themes
return unless themes.present?
themes_html_format(themes, "dashboard.unreachable_themes")
themes_html_format(themes, 'dashboard.unreachable_themes')
end
private
@ -291,8 +249,6 @@ class AdminDashboardData
"<li><a href=\"/admin/customize/themes/#{id}\">#{CGI.escapeHTML(name)}</a></li>"
end.join("\n")
message = I18n.t(i18n_key)
message += "<ul>#{html}</ul>"
message
"#{I18n.t(i18n_key)}<ul>#{html}</ul>"
end
end

View File

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

View File

@ -1,4 +1,4 @@
class AdminDashboardNextIndexData < AdminDashboardNextData
class AdminDashboardIndexData < AdminDashboardData
def get_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 "dashboard" => "dashboard_next#index"
get "dashboard/general" => "dashboard_next#general"
get "dashboard/moderation" => "dashboard_next#moderation"
get "dashboard/security" => "dashboard_next#security"
get "dashboard/reports" => "dashboard_next#reports"
get "dashboard-old" => "dashboard#index"
get "dashboard" => "dashboard#index"
get "dashboard/general" => "dashboard#general"
get "dashboard/moderation" => "dashboard#moderation"
get "dashboard/security" => "dashboard#security"
get "dashboard/reports" => "dashboard#reports"
resources :dashboard, only: [:index] do
collection do

View File

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