Skip to content

fix: Blank screen shown if server is unreachable; unsupported pages are accessible via direct URLs #2363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,21 +138,21 @@ export default class Dashboard extends React.Component {
if (error.code === 100) {
app.serverInfo = {
error: 'unable to connect to server',
enabledFeatures: {},
features: {},
parseServerVersion: 'unknown'
}
return Promise.resolve(app);
} else if (error.code === 107) {
app.serverInfo = {
error: 'server version too low',
enabledFeatures: {},
features: {},
parseServerVersion: 'unknown'
}
return Promise.resolve(app);
} else {
app.serverInfo = {
error: error.message || 'unknown error',
enabledFeatures: {},
features: {},
parseServerVersion: 'unknown'
}
return Promise.resolve(app);
Expand Down
21 changes: 21 additions & 0 deletions src/dashboard/Dashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,25 @@ body:global(.expanded) {
.content {
margin-left: $sidebarCollapsedWidth;
}
}

.loadingError {
font-size: 58px;
color: #ffffff;
}

.empty {
position: relative;
background: #1e3b4d;
min-height: 100vh;
text-align: center;
}

.cloud {
width: 170px;
height: 170px;
border-radius: 100%;
padding-top: 30px;
background: #3E5566;
margin: 0 auto 14px auto;
}
173 changes: 132 additions & 41 deletions src/dashboard/DashboardView.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,82 +8,125 @@
import React from 'react';
import Sidebar from 'components/Sidebar/Sidebar.react';
import styles from 'dashboard/Dashboard.scss';
import Icon from 'components/Icon/Icon.react';
import baseStyles from 'stylesheets/base.scss';
import Button from 'components/Button/Button.react';
import { CurrentApp } from 'context/currentApp';

export default class DashboardView extends React.Component {
static contextType = CurrentApp;

/* A DashboardView renders two pieces: the sidebar, and the app itself */

constructor() {
super();
this.state = {
route: '',
};
}

componentDidUpdate() {
this.onRouteChanged();
}
componentDidMount() {
this.onRouteChanged();
}

onRouteChanged() {
const appId = this.context.applicationId;
const path = this.props.location?.pathname ?? window.location.pathname;
const route = path.split(appId)[1].split('/')[1];
if (route !== this.state.route) {
this.setState({ route });
}
}

render() {
let sidebarChildren = null;
if (typeof this.renderSidebar === 'function') {
sidebarChildren = this.renderSidebar();
}
let appSlug = (this.context ? this.context.slug : '');
let appSlug = this.context ? this.context.slug : '';

if (!this.context.hasCheckedForMigraton) {
this.context.getMigrations().promise
.then(() => this.forceUpdate(), () => {});
this.context.getMigrations().promise.then(
() => this.forceUpdate(),
() => {}
);
}

let features = this.context.serverInfo.features;

let coreSubsections = [];
if (features.schemas &&
if (
features.schemas &&
features.schemas.addField &&
features.schemas.removeField &&
features.schemas.addClass &&
features.schemas.removeClass) {
features.schemas.removeClass
) {
coreSubsections.push({
name: 'Browser',
link: '/browser'
link: '/browser',
});
}

if (features.cloudCode && features.cloudCode.viewCode) {
coreSubsections.push({
name: 'Cloud Code',
link: '/cloud_code'
link: '/cloud_code',
});
}

//webhooks requires removal of heroku link code, then it should work.
if (features.hooks && features.hooks.create && features.hooks.read && features.hooks.update && features.hooks.delete) {
if (
features.hooks &&
features.hooks.create &&
features.hooks.read &&
features.hooks.update &&
features.hooks.delete
) {
coreSubsections.push({
name: 'Webhooks',
link: '/webhooks'
link: '/webhooks',
});
}

if (features.cloudCode && features.cloudCode.jobs) {
coreSubsections.push({
name: 'Jobs',
link: '/jobs'
link: '/jobs',
});
}

if (features.logs && Object.keys(features.logs).some(key => features.logs[key])) {
if (
features.logs &&
Object.keys(features.logs).some((key) => features.logs[key])
) {
coreSubsections.push({
name: 'Logs',
link: '/logs'
link: '/logs',
});
}

if (features.globalConfig &&
if (
features.globalConfig &&
features.globalConfig.create &&
features.globalConfig.read &&
features.globalConfig.update &&
features.globalConfig.delete) {
features.globalConfig.delete
) {
coreSubsections.push({
name: 'Config',
link: '/config'
link: '/config',
});
}

coreSubsections.push({
name: 'API Console',
link: '/api_console'
});
if (!this.context.serverInfo.error) {
coreSubsections.push({
name: 'API Console',
link: '/api_console',
});
}

if (this.context.migration) {
coreSubsections.push({
Expand All @@ -96,21 +139,21 @@ export default class DashboardView extends React.Component {
if (features.push && features.push.immediatePush) {
pushSubsections.push({
name: 'Send New Push',
link: '/push/new'
link: '/push/new',
});
}

if (features.push && features.push.storedPushData) {
pushSubsections.push({
name: 'Past Pushes',
link: '/push/activity'
link: '/push/activity',
});
}

if (features.push && features.push.pushAudiences) {
pushSubsections.push({
name: 'Audiences',
link: '/push/audiences'
link: '/push/audiences',
});
}

Expand Down Expand Up @@ -195,7 +238,7 @@ export default class DashboardView extends React.Component {
});
}*/

let appSidebarSections = []
let appSidebarSections = [];

if (coreSubsections.length > 0) {
appSidebarSections.push({
Expand All @@ -211,7 +254,7 @@ export default class DashboardView extends React.Component {
name: 'Push',
icon: 'push-outline',
link: '/push',
style: {paddingLeft: '16px'},
style: { paddingLeft: '16px' },
subsections: pushSubsections,
});
}
Expand All @@ -221,7 +264,7 @@ export default class DashboardView extends React.Component {
name: 'Analytics',
icon: 'analytics-outline',
link: '/analytics',
subsections: analyticsSidebarSections
subsections: analyticsSidebarSections,
});
}

Expand All @@ -230,29 +273,77 @@ export default class DashboardView extends React.Component {
name: 'App Settings',
icon: 'gear-solid',
link: '/settings',
subsections: settingsSections
subsections: settingsSections,
});
}

let sidebar = (
<Sidebar
sections={appSidebarSections}
appSelector={true}
section={this.section}
subsection={this.subsection}
prefix={'/apps/' + appSlug}
action={this.action}
primaryBackgroundColor={this.context.primaryBackgroundColor}
secondaryBackgroundColor={this.context.secondaryBackgroundColor}
<Sidebar
sections={appSidebarSections}
appSelector={true}
section={this.section}
subsection={this.subsection}
prefix={'/apps/' + appSlug}
action={this.action}
primaryBackgroundColor={this.context.primaryBackgroundColor}
secondaryBackgroundColor={this.context.secondaryBackgroundColor}
>
{sidebarChildren}
</Sidebar>);
{sidebarChildren}
</Sidebar>
);

let content = <div className={styles.content}>{this.renderContent()}</div>;
const canRoute = [...coreSubsections, ...pushSubsections]
.map(({ link }) => link.split('/')[1])
.includes(this.state.route);

if (!canRoute) {
content = (
<div className={styles.empty}>
<div className={baseStyles.center}>
<div className={styles.cloud}>
<Icon
width={110}
height={110}
name="cloud-surprise"
fill="#1e3b4d"
/>
</div>
<div className={styles.loadingError}>Feature unavailable</div>
</div>
</div>
);
}

if (this.context.serverInfo.error) {
content = (
<div className={styles.empty}>
<div className={baseStyles.center}>
<div className={styles.cloud}>
<Icon
width={110}
height={110}
name="cloud-surprise"
fill="#1e3b4d"
/>
</div>
<div className={styles.loadingError}>
{this.context.serverInfo.error.replace(/-/g, '\u2011')}
</div>
<Button
color="white"
value="Reload"
width="120px"
onClick={() => location.reload()}
/>
</div>
</div>
);
}

return (
<div className={styles.dashboard}>
<div className={styles.content}>
{this.renderContent()}
</div>
{content}
{sidebar}
</div>
);
Expand Down