mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 23:58:31 -05:00
Merge pull request #2455 from riking/error-route
Fancy error page for topic lists
This commit is contained in:
commit
7ac4372427
16 changed files with 269 additions and 9 deletions
107
app/assets/javascripts/discourse/controllers/exception.js.es6
Normal file
107
app/assets/javascripts/discourse/controllers/exception.js.es6
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
var ButtonBackBright = {
|
||||||
|
classes: "btn-primary",
|
||||||
|
action: "back",
|
||||||
|
key: "errors.buttons.back"
|
||||||
|
},
|
||||||
|
ButtonBackDim = {
|
||||||
|
classes: "",
|
||||||
|
action: "back",
|
||||||
|
key: "errors.buttons.back"
|
||||||
|
},
|
||||||
|
ButtonTryAgain = {
|
||||||
|
classes: "btn-primary",
|
||||||
|
action: "tryLoading",
|
||||||
|
key: "errors.buttons.again"
|
||||||
|
},
|
||||||
|
ButtonLoadPage = {
|
||||||
|
classes: "btn-primary",
|
||||||
|
action: "tryLoading",
|
||||||
|
key: "errors.buttons.fixed"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
The controller for the nice error page
|
||||||
|
|
||||||
|
@class ExceptionController
|
||||||
|
@extends Discourse.ObjectController
|
||||||
|
@namespace Discourse
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
export default Discourse.ObjectController.extend({
|
||||||
|
thrown: null,
|
||||||
|
lastTransition: null,
|
||||||
|
|
||||||
|
isNetwork: function() {
|
||||||
|
// never made it on the wire
|
||||||
|
if (this.get('thrown.readyState') === 0) return true;
|
||||||
|
// timed out
|
||||||
|
if (this.get('thrown.jqTextStatus') === "timeout") return true;
|
||||||
|
return false;
|
||||||
|
}.property(),
|
||||||
|
isServer: Em.computed.gte('thrown.status', 500),
|
||||||
|
isUnknown: Em.computed.none('isNetwork', 'isServer'),
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// make ajax requests to /srv/status with exponential backoff
|
||||||
|
// if one succeeds, set networkFixed to true, which puts a "Fixed!" message on the page
|
||||||
|
networkFixed: false,
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
this.set('loading', false);
|
||||||
|
}.on('init'),
|
||||||
|
|
||||||
|
reason: function() {
|
||||||
|
if (this.get('isNetwork')) {
|
||||||
|
return I18n.t('errors.reasons.network');
|
||||||
|
} else if (this.get('isServer')) {
|
||||||
|
return I18n.t('errors.reasons.server');
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
return I18n.t('errors.reasons.unknown');
|
||||||
|
}
|
||||||
|
}.property('isNetwork', 'isServer', 'isUnknown'),
|
||||||
|
|
||||||
|
requestUrl: Em.computed.alias('thrown.requestedUrl'),
|
||||||
|
|
||||||
|
desc: function() {
|
||||||
|
if (this.get('networkFixed')) {
|
||||||
|
return I18n.t('errors.desc.network_fixed');
|
||||||
|
} else if (this.get('isNetwork')) {
|
||||||
|
return I18n.t('errors.desc.network');
|
||||||
|
} else if (this.get('isServer')) {
|
||||||
|
return I18n.t('errors.desc.server', this.get('thrown.statusText'));
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
return I18n.t('errors.desc.unknown');
|
||||||
|
}
|
||||||
|
}.property('networkFixed', 'isNetwork', 'isServer', 'isUnknown'),
|
||||||
|
|
||||||
|
enabledButtons: function() {
|
||||||
|
if (this.get('networkFixed')) {
|
||||||
|
return [ButtonLoadPage];
|
||||||
|
} else if (this.get('isNetwork')) {
|
||||||
|
return [ButtonBackDim, ButtonTryAgain];
|
||||||
|
} else if (this.get('isServer')) {
|
||||||
|
return [ButtonBackBright];
|
||||||
|
} else {
|
||||||
|
return [ButtonBackBright, ButtonTryAgain];
|
||||||
|
}
|
||||||
|
}.property('networkFixed', 'isNetwork', 'isServer', 'isUnknown'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
back: function() {
|
||||||
|
window.history.back();
|
||||||
|
},
|
||||||
|
|
||||||
|
tryLoading: function() {
|
||||||
|
this.set('loading', true);
|
||||||
|
var self = this;
|
||||||
|
Em.run.schedule('afterRender', function() {
|
||||||
|
self.get('lastTransition').retry();
|
||||||
|
self.set('loading', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -385,6 +385,16 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
retryLoading: function() {
|
||||||
|
var self = this;
|
||||||
|
self.set('retrying', true);
|
||||||
|
this.get('postStream').refresh().then(function() {
|
||||||
|
self.set('retrying', false);
|
||||||
|
}, function() {
|
||||||
|
self.set('retrying', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
toggleWiki: function(post) {
|
toggleWiki: function(post) {
|
||||||
post.toggleProperty('wiki');
|
post.toggleProperty('wiki');
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ Discourse.Ajax = Em.Mixin.create({
|
||||||
};
|
};
|
||||||
|
|
||||||
var oldError = args.error;
|
var oldError = args.error;
|
||||||
args.error = function(xhr) {
|
args.error = function(xhr, textStatus) {
|
||||||
|
|
||||||
// note: for bad CSRF we don't loop an extra request right away.
|
// note: for bad CSRF we don't loop an extra request right away.
|
||||||
// this allows us to eliminate the possibility of having a loop.
|
// this allows us to eliminate the possibility of having a loop.
|
||||||
|
@ -62,9 +62,13 @@ Discourse.Ajax = Em.Mixin.create({
|
||||||
Discourse.Session.current().set('csrfToken', null);
|
Discourse.Session.current().set('csrfToken', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a parseerror, don't reject
|
// If it's a parsererror, don't reject
|
||||||
if (xhr.status === 200) return args.success(xhr);
|
if (xhr.status === 200) return args.success(xhr);
|
||||||
|
|
||||||
|
// Fill in some extra info
|
||||||
|
xhr.jqTextStatus = textStatus;
|
||||||
|
xhr.requestedUrl = url;
|
||||||
|
|
||||||
Ember.run(promise, promise.reject, xhr);
|
Ember.run(promise, promise.reject, xhr);
|
||||||
if (oldError) oldError(xhr);
|
if (oldError) oldError(xhr);
|
||||||
};
|
};
|
||||||
|
|
|
@ -249,6 +249,7 @@ Discourse.PostStream = Em.Object.extend({
|
||||||
self.setProperties({ loadingFilter: false, loaded: true });
|
self.setProperties({ loadingFilter: false, loaded: true });
|
||||||
}).catch(function(result) {
|
}).catch(function(result) {
|
||||||
self.errorLoading(result);
|
self.errorLoading(result);
|
||||||
|
throw result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
hasLoadedData: Em.computed.and('hasPosts', 'hasStream'),
|
hasLoadedData: Em.computed.and('hasPosts', 'hasStream'),
|
||||||
|
|
|
@ -10,6 +10,19 @@ Discourse.ApplicationRoute = Em.Route.extend({
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
|
error: function(err, transition) {
|
||||||
|
if (err.status === 404) {
|
||||||
|
// 404
|
||||||
|
this.intermediateTransitionTo('unknown');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var exceptionController = this.controllerFor('exception');
|
||||||
|
exceptionController.setProperties({ lastTransition: transition, thrown: err });
|
||||||
|
|
||||||
|
this.intermediateTransitionTo('exception');
|
||||||
|
},
|
||||||
|
|
||||||
showLogin: function() {
|
showLogin: function() {
|
||||||
if (Discourse.get("isReadOnly")) {
|
if (Discourse.get("isReadOnly")) {
|
||||||
bootbox.alert(I18n.t("read_only_mode.login_disabled"));
|
bootbox.alert(I18n.t("read_only_mode.login_disabled"));
|
||||||
|
|
|
@ -13,6 +13,9 @@ Discourse.Route.buildRoutes(function() {
|
||||||
router.route(page, { path: '/' + page });
|
router.route(page, { path: '/' + page });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Error page
|
||||||
|
this.route('exception', { path: '/exception' });
|
||||||
|
|
||||||
// Topic routes
|
// Topic routes
|
||||||
this.resource('topic', { path: '/t/:slug/:id' }, function() {
|
this.resource('topic', { path: '/t/:slug/:id' }, function() {
|
||||||
this.route('fromParams', { path: '/' });
|
this.route('fromParams', { path: '/' });
|
||||||
|
|
13
app/assets/javascripts/discourse/routes/exception_route.js
Normal file
13
app/assets/javascripts/discourse/routes/exception_route.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
Client-side pseudo-route for showing an error page.
|
||||||
|
|
||||||
|
@class ExceptionRoute
|
||||||
|
@extends Discourse.Route
|
||||||
|
@namespace Discourse
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.ExceptionRoute = Discourse.Route.extend({
|
||||||
|
serialize: function() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
});
|
|
@ -89,7 +89,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
||||||
}
|
}
|
||||||
}, 150),
|
}, 150),
|
||||||
|
|
||||||
willTransition: function() { this.set("isTransitioning", true); }
|
willTransition: function() { this.set("isTransitioning", true); return true; }
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<div class="container">
|
||||||
|
<div class="error-page">
|
||||||
|
<div class="face">:(</div>
|
||||||
|
<div class="reason">{{reason}}</div>
|
||||||
|
<div class="url">
|
||||||
|
{{i18n errors.prev_page}} <a {{bind-attr href=requestUrl}} data-auto-route="true">{{requestUrl}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="desc">
|
||||||
|
{{#if networkFixed}}
|
||||||
|
<i class="fa fa-check-circle"></i>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{desc}}
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
{{#each buttonData in enabledButtons}}
|
||||||
|
<button class="btn {{unbound buttonData.classes}}" {{action buttonData.action}}>{{boundI18n buttonData.key}}</button>
|
||||||
|
{{/each}}
|
||||||
|
{{#if loading}}
|
||||||
|
<i class="fa fa-spin fa-spinner"></i>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -117,17 +117,19 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if hasError}}
|
{{#if hasError}}
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
{{#if errorBodyHtml}}
|
<div class="topic-error">
|
||||||
{{{errorBodyHtml}}}
|
{{{errorBodyHtml}}}
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if message}}
|
{{#if message}}
|
||||||
<div class="message">
|
{{message}}
|
||||||
<h2>{{message}}</h2>
|
|
||||||
{{#unless currentUser}}
|
{{#unless currentUser}}
|
||||||
<button {{action showLogin}} class='btn btn-primary btn-small'>{{i18n log_in}}</button>
|
<button {{action showLogin}} class='btn btn-primary btn-small'>{{i18n log_in}}</button>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
<button class="btn btn-primary topic-retry" {{action retryLoading}}>{{i18n errors.buttons.again}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
{{#if retrying}}
|
||||||
|
<div class='spinner'>{{i18n loading}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
30
app/assets/stylesheets/common/base/exception.scss
Normal file
30
app/assets/stylesheets/common/base/exception.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
.error-page {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2em;
|
||||||
|
|
||||||
|
.face {
|
||||||
|
font-size: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
.reason {
|
||||||
|
font-size: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.url {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
margin-top: 16px;
|
||||||
|
.fa-check-circle {
|
||||||
|
color: $success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,6 +84,23 @@ a:hover.reply-new {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topic-error {
|
||||||
|
padding: 18px;
|
||||||
|
width: 60%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.1em;
|
||||||
|
|
||||||
|
.topic-retry {
|
||||||
|
display: block;
|
||||||
|
margin-top: 28px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#topic-closing-info {
|
#topic-closing-info {
|
||||||
border-top: 1px solid scale-color-diff();
|
border-top: 1px solid scale-color-diff();
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
|
|
@ -146,6 +146,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topic-error {
|
||||||
|
padding: 18px;
|
||||||
|
width: 90%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.1em;
|
||||||
|
|
||||||
|
.topic-retry {
|
||||||
|
display: block;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#topic-progress-wrapper.docked {
|
#topic-progress-wrapper.docked {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,4 +16,8 @@ class ForumsController < ApplicationController
|
||||||
raise "WAT - #{Time.now.to_s}"
|
raise "WAT - #{Time.now.to_s}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def home_redirect
|
||||||
|
redirect_to '/'
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -468,6 +468,21 @@ en:
|
||||||
the_topic: "the topic"
|
the_topic: "the topic"
|
||||||
|
|
||||||
loading: "Loading..."
|
loading: "Loading..."
|
||||||
|
errors:
|
||||||
|
prev_page: "while trying to load"
|
||||||
|
reasons:
|
||||||
|
network: "Network Error"
|
||||||
|
server: "Server Error: {{code}}"
|
||||||
|
unknown: "Error"
|
||||||
|
desc:
|
||||||
|
network: "Please check your connection."
|
||||||
|
network_fixed: "Looks like it's back."
|
||||||
|
server: "Something went wrong."
|
||||||
|
unknown: "Something went wrong."
|
||||||
|
buttons:
|
||||||
|
back: "Go Back"
|
||||||
|
again: "Try Again"
|
||||||
|
fixed: "Load Page"
|
||||||
close: "Close"
|
close: "Close"
|
||||||
assets_changed_confirm: "This site was just updated. Refresh now for the latest version?"
|
assets_changed_confirm: "This site was just updated. Refresh now for the latest version?"
|
||||||
read_only_mode:
|
read_only_mode:
|
||||||
|
|
|
@ -377,6 +377,7 @@ Discourse::Application.routes.draw do
|
||||||
get "onebox" => "onebox#show"
|
get "onebox" => "onebox#show"
|
||||||
|
|
||||||
get "error" => "forums#error"
|
get "error" => "forums#error"
|
||||||
|
get "exception" => "list#latest"
|
||||||
|
|
||||||
get "message-bus/poll" => "message_bus#poll"
|
get "message-bus/poll" => "message_bus#poll"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue