diff --git a/CHANGELOG.md b/CHANGELOG.md index 156a6e0a4..f3592bb65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Tags: Ability to set the tags page as the home page. - `bidi` attribute for Mithril elements as a shortcut to set up bidirectional bindings. - Abstract SettingsModal component for quickly building admin config modals. +- "Debug" button to inspect the response of a failed AJAX request. ### Changed - Migrations must be namespaced under `Flarum\Migrations\{Core|ExtensionName}`. ([#422](https://github.com/flarum/core/issues/422)) diff --git a/js/lib/App.js b/js/lib/App.js index 1e00cd992..627c3c2ac 100644 --- a/js/lib/App.js +++ b/js/lib/App.js @@ -1,8 +1,11 @@ import ItemList from 'flarum/utils/ItemList'; import Alert from 'flarum/components/Alert'; +import Button from 'flarum/components/Button'; +import RequestErrorModal from 'flarum/components/RequestErrorModal'; import Translator from 'flarum/Translator'; import extract from 'flarum/utils/extract'; import patchMithril from 'flarum/utils/patchMithril'; +import RequestError from 'flarum/utils/RequestError'; /** * The `App` class provides a container for an application, as well as various @@ -193,7 +196,7 @@ export default class App { try { return JSON.parse(responseText); } catch (e) { - throw new Error('Oops! Something went wrong on the server. Please reload the page and try again.'); + throw new RequestError(e.message, responseText); } }); @@ -202,35 +205,52 @@ export default class App { // awry. const original = options.extract; options.extract = xhr => { + let responseText; + + if (original) { + responseText = original(xhr.responseText); + } else { + responseText = xhr.responseText.length > 0 ? xhr.responseText : null; + } + const status = xhr.status; if (status >= 500 && status <= 599) { - throw new Error('Oops! Something went wrong on the server. Please reload the page and try again.'); + throw new RequestError('Internal Server Error', responseText); } - if (original) { - return original(xhr.responseText); - } - - return xhr.responseText.length > 0 ? xhr.responseText : null; + return responseText; }; - this.alerts.dismiss(this.requestError); + this.alerts.dismiss(this.requestErrorAlert); // Now make the request. If it's a failure, inspect the error that was // returned and show an alert containing its contents. - return m.request(options).then(null, response => { - if (response instanceof Error) { - this.alerts.show(this.requestError = new Alert({ + return m.request(options).then(null, error => { + if (error instanceof RequestError) { + this.alerts.show(this.requestErrorAlert = new Alert({ type: 'error', - children: response.message + children: 'Oops! Something went wrong. Please reload the page and try again.', + controls: app.forum.attribute('debug') ? [ + <Button className="Button Button--link" onclick={this.showDebug.bind(this, error)}>Debug</Button> + ] : undefined })); } - throw response; + throw error; }); } + /** + * @param {RequestError} error + * @private + */ + showDebug(error) { + this.alerts.dismiss(this.requestErrorAlert); + + this.modal.show(new RequestErrorModal({error})); + } + /** * Show alert error messages for each error returned in an API response. * diff --git a/js/lib/components/RequestErrorModal.js b/js/lib/components/RequestErrorModal.js new file mode 100644 index 000000000..ed84ec7b8 --- /dev/null +++ b/js/lib/components/RequestErrorModal.js @@ -0,0 +1,25 @@ +import Modal from 'flarum/components/Modal'; + +export default class RequestErrorModal extends Modal { + className() { + return 'RequestErrorModal Modal--large'; + } + + title() { + return this.props.error.message; + } + + content() { + let responseText; + + try { + responseText = JSON.stringify(JSON.parse(this.props.error.responseText), null, 2); + } catch (e) { + responseText = this.props.error.responseText; + } + + return <div className="Modal-body"> + <pre>{responseText}</pre> + </div>; + } +} diff --git a/js/lib/utils/RequestError.js b/js/lib/utils/RequestError.js new file mode 100644 index 000000000..f4ee196fb --- /dev/null +++ b/js/lib/utils/RequestError.js @@ -0,0 +1,6 @@ +export default class RequestError { + constructor(message, responseText) { + this.message = message; + this.responseText = responseText; + } +} diff --git a/less/lib/Alert.less b/less/lib/Alert.less index f034a237d..7f493fcac 100755 --- a/less/lib/Alert.less +++ b/less/lib/Alert.less @@ -4,21 +4,21 @@ background: @alert-bg; line-height: 1.5; - &, button, button:hover { + &, .Button, .Button:hover { color: @alert-color; } } .Alert--error { background: @alert-error-bg; - &, a, a:hover, button, button:hover { + &, a, a:hover, .Button, .Button:hover { color: @alert-error-color; } } .Alert--success { background: @alert-success-bg; - &, a, a:hover, button, button:hover { + &, a, a:hover, .Button, .Button:hover { color: @alert-success-color; } a, a:hover { @@ -35,7 +35,7 @@ display: inline-block; margin: 0 5px; - > a { + > a, > .Button { text-transform: uppercase; font-size: 12px; font-weight: bold; @@ -45,6 +45,9 @@ text-decoration: none; opacity: 0.5; } + &:hover { + text-decoration: underline; + } } > .Button { diff --git a/less/lib/scaffolding.less b/less/lib/scaffolding.less index f1d0aa4ea..1e2ad4e0d 100755 --- a/less/lib/scaffolding.less +++ b/less/lib/scaffolding.less @@ -130,3 +130,10 @@ blockquote ol:last-child { position: fixed; } } + +.RequestErrorModal { + pre { + white-space: pre-wrap; + margin: 0; + } +}