bring back children arrow return in PageErrorHandlingWrapper to prevent NPE errors
This commit is contained in:
parent
ecf54678ac
commit
75c7d2bb56
8 changed files with 471 additions and 448 deletions
|
|
@ -36,7 +36,7 @@ export default function PageErrorHandlingWrapper({
|
|||
objectName?: string;
|
||||
pageName: string;
|
||||
itemNotFoundMessage?: string;
|
||||
children: React.ReactNode;
|
||||
children: () => React.ReactNode;
|
||||
}): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!errorData) {
|
||||
|
|
|
|||
|
|
@ -143,79 +143,81 @@ class EscalationChainsPage extends React.Component<EscalationChainsPageProps, Es
|
|||
pageName="escalations"
|
||||
itemNotFoundMessage={`Escalation chain with id=${query?.id} is not found. Please select escalation chain from the list.`}
|
||||
>
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('filters')}>
|
||||
<EscalationsFilters value={escalationChainsFilters} onChange={this.handleEscalationsFiltersChange} />
|
||||
</div>
|
||||
{!searchResult || searchResult.length ? (
|
||||
<div className={cx('escalations')}>
|
||||
<div className={cx('left-column')}>
|
||||
<WithPermissionControl userAction={UserAction.UpdateAlertReceiveChannels}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({ showCreateEscalationChainModal: true });
|
||||
}}
|
||||
icon="plus"
|
||||
className={cx('new-escalation-chain')}
|
||||
>
|
||||
New escalation chain
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
<div className={cx('escalations-list')}>
|
||||
{searchResult ? (
|
||||
<GList
|
||||
autoScroll
|
||||
selectedId={selectedEscalationChain}
|
||||
items={searchResult}
|
||||
itemKey="id"
|
||||
onSelect={this.setSelectedEscalationChain}
|
||||
>
|
||||
{(item) => <EscalationChainCard id={item.id} />}
|
||||
</GList>
|
||||
) : (
|
||||
<LoadingPlaceholder className={cx('loading')} text="Loading..." />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx('escalation')}>{this.renderEscalation()}</div>
|
||||
{() => (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('filters')}>
|
||||
<EscalationsFilters value={escalationChainsFilters} onChange={this.handleEscalationsFiltersChange} />
|
||||
</div>
|
||||
) : (
|
||||
<Tutorial
|
||||
step={TutorialStep.Escalations}
|
||||
title={
|
||||
<VerticalGroup align="center" spacing="lg">
|
||||
<Text type="secondary">No escalations found, check your filtering and current team.</Text>
|
||||
<WithPermissionControl userAction={UserAction.UpdateEscalationPolicies}>
|
||||
{!searchResult || searchResult.length ? (
|
||||
<div className={cx('escalations')}>
|
||||
<div className={cx('left-column')}>
|
||||
<WithPermissionControl userAction={UserAction.UpdateAlertReceiveChannels}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
this.setState({ showCreateEscalationChainModal: true });
|
||||
}}
|
||||
icon="plus"
|
||||
className={cx('new-escalation-chain')}
|
||||
>
|
||||
New Escalation Chain
|
||||
New escalation chain
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</VerticalGroup>
|
||||
}
|
||||
<div className={cx('escalations-list')}>
|
||||
{searchResult ? (
|
||||
<GList
|
||||
autoScroll
|
||||
selectedId={selectedEscalationChain}
|
||||
items={searchResult}
|
||||
itemKey="id"
|
||||
onSelect={this.setSelectedEscalationChain}
|
||||
>
|
||||
{(item) => <EscalationChainCard id={item.id} />}
|
||||
</GList>
|
||||
) : (
|
||||
<LoadingPlaceholder className={cx('loading')} text="Loading..." />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx('escalation')}>{this.renderEscalation()}</div>
|
||||
</div>
|
||||
) : (
|
||||
<Tutorial
|
||||
step={TutorialStep.Escalations}
|
||||
title={
|
||||
<VerticalGroup align="center" spacing="lg">
|
||||
<Text type="secondary">No escalations found, check your filtering and current team.</Text>
|
||||
<WithPermissionControl userAction={UserAction.UpdateEscalationPolicies}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
this.setState({ showCreateEscalationChainModal: true });
|
||||
}}
|
||||
>
|
||||
New Escalation Chain
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</VerticalGroup>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showCreateEscalationChainModal && (
|
||||
<EscalationChainForm
|
||||
escalationChainId={escalationChainIdToCopy}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
showCreateEscalationChainModal: false,
|
||||
escalationChainIdToCopy: undefined,
|
||||
});
|
||||
}}
|
||||
onUpdate={this.handleEscalationChainCreate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showCreateEscalationChainModal && (
|
||||
<EscalationChainForm
|
||||
escalationChainId={escalationChainIdToCopy}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
showCreateEscalationChainModal: false,
|
||||
escalationChainIdToCopy: undefined,
|
||||
});
|
||||
}}
|
||||
onUpdate={this.handleEscalationChainCreate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -129,65 +129,67 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
|
|||
return (
|
||||
<PluginPage pageNav={pages['incident'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper errorData={errorData} objectName="alert group" pageName="incidents">
|
||||
<div className={cx('root')}>
|
||||
{errorData.isNotFoundError ? (
|
||||
<div className={cx('not-found')}>
|
||||
<VerticalGroup spacing="lg" align="center">
|
||||
<Text.Title level={1}>404</Text.Title>
|
||||
<Text.Title level={4}>Incident not found</Text.Title>
|
||||
<PluginLink query={{ page: 'incidents', cursor, start, perpage }}>
|
||||
<Button variant="secondary" icon="arrow-left" size="md">
|
||||
Go to incidents page
|
||||
</Button>
|
||||
</PluginLink>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{this.renderHeader()}
|
||||
<div className={cx('content')}>
|
||||
<div className={cx('column')}>
|
||||
<Incident incident={incident} datetimeReference={this.getIncidentDatetimeReference(incident)} />
|
||||
<GroupedIncidentsList
|
||||
id={incident.pk}
|
||||
getIncidentDatetimeReference={this.getIncidentDatetimeReference}
|
||||
/>
|
||||
<AttachedIncidentsList id={incident.pk} getUnattachClickHandler={this.getUnattachClickHandler} />
|
||||
</div>
|
||||
<div className={cx('column')}>{this.renderTimeline()}</div>
|
||||
{() => (
|
||||
<div className={cx('root')}>
|
||||
{errorData.isNotFoundError ? (
|
||||
<div className={cx('not-found')}>
|
||||
<VerticalGroup spacing="lg" align="center">
|
||||
<Text.Title level={1}>404</Text.Title>
|
||||
<Text.Title level={4}>Incident not found</Text.Title>
|
||||
<PluginLink query={{ page: 'incidents', cursor, start, perpage }}>
|
||||
<Button variant="secondary" icon="arrow-left" size="md">
|
||||
Go to incidents page
|
||||
</Button>
|
||||
</PluginLink>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
{showIntegrationSettings && (
|
||||
<IntegrationSettings
|
||||
alertGroupId={incident.pk}
|
||||
onUpdate={() => {
|
||||
alertReceiveChannelStore.updateItem(incident.alert_receive_channel.id);
|
||||
}}
|
||||
onUpdateTemplates={() => {
|
||||
store.alertGroupStore.getAlert(id);
|
||||
}}
|
||||
startTab={IntegrationSettingsTab.Templates}
|
||||
id={incident.alert_receive_channel.id}
|
||||
onHide={() =>
|
||||
this.setState({
|
||||
showIntegrationSettings: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showAttachIncidentForm && (
|
||||
<AttachIncidentForm
|
||||
id={id}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
showAttachIncidentForm: false,
|
||||
});
|
||||
}}
|
||||
onUpdate={this.update}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{this.renderHeader()}
|
||||
<div className={cx('content')}>
|
||||
<div className={cx('column')}>
|
||||
<Incident incident={incident} datetimeReference={this.getIncidentDatetimeReference(incident)} />
|
||||
<GroupedIncidentsList
|
||||
id={incident.pk}
|
||||
getIncidentDatetimeReference={this.getIncidentDatetimeReference}
|
||||
/>
|
||||
<AttachedIncidentsList id={incident.pk} getUnattachClickHandler={this.getUnattachClickHandler} />
|
||||
</div>
|
||||
<div className={cx('column')}>{this.renderTimeline()}</div>
|
||||
</div>
|
||||
{showIntegrationSettings && (
|
||||
<IntegrationSettings
|
||||
alertGroupId={incident.pk}
|
||||
onUpdate={() => {
|
||||
alertReceiveChannelStore.updateItem(incident.alert_receive_channel.id);
|
||||
}}
|
||||
onUpdateTemplates={() => {
|
||||
store.alertGroupStore.getAlert(id);
|
||||
}}
|
||||
startTab={IntegrationSettingsTab.Templates}
|
||||
id={incident.alert_receive_channel.id}
|
||||
onHide={() =>
|
||||
this.setState({
|
||||
showIntegrationSettings: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showAttachIncidentForm && (
|
||||
<AttachIncidentForm
|
||||
id={id}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
showAttachIncidentForm: false,
|
||||
});
|
||||
}}
|
||||
onUpdate={this.update}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -104,10 +104,12 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
|
|||
return (
|
||||
<PluginPage pageNav={pages['incidents'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper pageName="incidents">
|
||||
<div className={cx('root')}>
|
||||
{this.renderIncidentFilters()}
|
||||
{this.renderTable()}
|
||||
</div>
|
||||
{() => (
|
||||
<div className={cx('root')}>
|
||||
{this.renderIncidentFilters()}
|
||||
{this.renderTable()}
|
||||
</div>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -139,110 +139,112 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
pageName="integrations"
|
||||
itemNotFoundMessage={`Integration with id=${query?.id} is not found. Please select integration from the list.`}
|
||||
>
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('filters')}>
|
||||
<IntegrationsFilters value={integrationsFilters} onChange={this.handleIntegrationsFiltersChange} />
|
||||
</div>
|
||||
{searchResult?.length ? (
|
||||
<div className={cx('integrations')}>
|
||||
<div className={cx('integrationsList')}>
|
||||
<WithPermissionControl userAction={UserAction.UpdateAlertReceiveChannels}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({ showCreateIntegrationModal: true });
|
||||
}}
|
||||
icon="plus"
|
||||
className={cx('newIntegrationButton')}
|
||||
>
|
||||
New integration for receiving alerts
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
<div className={cx('alert-receive-channels-list')}>
|
||||
<GList
|
||||
autoScroll
|
||||
selectedId={store.selectedAlertReceiveChannel}
|
||||
items={searchResult}
|
||||
itemKey="id"
|
||||
onSelect={this.handleAlertReceiveChannelSelect}
|
||||
>
|
||||
{(item) => (
|
||||
<AlertReceiveChannelCard
|
||||
id={item.id}
|
||||
onShowHeartbeatModal={() => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: item.id,
|
||||
integrationSettingsTab: IntegrationSettingsTab.Heartbeat,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</GList>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx('alert-rules', 'alertRulesBorder')}>
|
||||
<AlertRules
|
||||
alertReceiveChannelId={store.selectedAlertReceiveChannel}
|
||||
onDelete={this.handleDeleteAlertReceiveChannel}
|
||||
onShowSettings={(integrationSettingsTab?: IntegrationSettingsTab) => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: store.selectedAlertReceiveChannel,
|
||||
integrationSettingsTab,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{() => (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('filters')}>
|
||||
<IntegrationsFilters value={integrationsFilters} onChange={this.handleIntegrationsFiltersChange} />
|
||||
</div>
|
||||
) : searchResult ? (
|
||||
<Tutorial
|
||||
step={TutorialStep.Integrations}
|
||||
title={
|
||||
<VerticalGroup align="center" spacing="lg">
|
||||
<Text type="secondary">No integrations found. Review your filter and team settings.</Text>
|
||||
{searchResult?.length ? (
|
||||
<div className={cx('integrations')}>
|
||||
<div className={cx('integrationsList')}>
|
||||
<WithPermissionControl userAction={UserAction.UpdateAlertReceiveChannels}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
this.setState({ showCreateIntegrationModal: true });
|
||||
}}
|
||||
icon="plus"
|
||||
className={cx('newIntegrationButton')}
|
||||
>
|
||||
New integration for receiving alerts
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</VerticalGroup>
|
||||
}
|
||||
<div className={cx('alert-receive-channels-list')}>
|
||||
<GList
|
||||
autoScroll
|
||||
selectedId={store.selectedAlertReceiveChannel}
|
||||
items={searchResult}
|
||||
itemKey="id"
|
||||
onSelect={this.handleAlertReceiveChannelSelect}
|
||||
>
|
||||
{(item) => (
|
||||
<AlertReceiveChannelCard
|
||||
id={item.id}
|
||||
onShowHeartbeatModal={() => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: item.id,
|
||||
integrationSettingsTab: IntegrationSettingsTab.Heartbeat,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</GList>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx('alert-rules', 'alertRulesBorder')}>
|
||||
<AlertRules
|
||||
alertReceiveChannelId={store.selectedAlertReceiveChannel}
|
||||
onDelete={this.handleDeleteAlertReceiveChannel}
|
||||
onShowSettings={(integrationSettingsTab?: IntegrationSettingsTab) => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: store.selectedAlertReceiveChannel,
|
||||
integrationSettingsTab,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : searchResult ? (
|
||||
<Tutorial
|
||||
step={TutorialStep.Integrations}
|
||||
title={
|
||||
<VerticalGroup align="center" spacing="lg">
|
||||
<Text type="secondary">No integrations found. Review your filter and team settings.</Text>
|
||||
<WithPermissionControl userAction={UserAction.UpdateAlertReceiveChannels}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
this.setState({ showCreateIntegrationModal: true });
|
||||
}}
|
||||
>
|
||||
New integration for receiving alerts
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</VerticalGroup>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<LoadingPlaceholder text="Loading..." />
|
||||
)}
|
||||
</div>
|
||||
{alertReceiveChannelToShowSettings && (
|
||||
<IntegrationSettings
|
||||
onUpdate={() => {
|
||||
alertReceiveChannelStore.updateItem(alertReceiveChannelToShowSettings);
|
||||
}}
|
||||
startTab={integrationSettingsTab}
|
||||
id={alertReceiveChannelToShowSettings}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: undefined,
|
||||
integrationSettingsTab: undefined,
|
||||
});
|
||||
LocationHelper.update({ tab: undefined }, 'partial');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LoadingPlaceholder text="Loading..." />
|
||||
)}
|
||||
</div>
|
||||
{alertReceiveChannelToShowSettings && (
|
||||
<IntegrationSettings
|
||||
onUpdate={() => {
|
||||
alertReceiveChannelStore.updateItem(alertReceiveChannelToShowSettings);
|
||||
}}
|
||||
startTab={integrationSettingsTab}
|
||||
id={alertReceiveChannelToShowSettings}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: undefined,
|
||||
integrationSettingsTab: undefined,
|
||||
});
|
||||
LocationHelper.update({ tab: undefined }, 'partial');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showCreateIntegrationModal && (
|
||||
<CreateAlertReceiveChannelContainer
|
||||
onHide={() => {
|
||||
this.setState({ showCreateIntegrationModal: false });
|
||||
}}
|
||||
onCreate={this.handleCreateNewAlertReceiveChannel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{showCreateIntegrationModal && (
|
||||
<CreateAlertReceiveChannelContainer
|
||||
onHide={() => {
|
||||
this.setState({ showCreateIntegrationModal: false });
|
||||
}}
|
||||
onCreate={this.handleCreateNewAlertReceiveChannel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -118,43 +118,45 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
pageName="outgoing_webhooks"
|
||||
itemNotFoundMessage={`Outgoing webhook with id=${query?.id} is not found. Please select outgoing webhook from the list.`}
|
||||
>
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<GTable
|
||||
emptyText={webhooks ? 'No outgoing webhooks found' : 'Loading...'}
|
||||
title={() => (
|
||||
<div className={cx('header')}>
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Outgoing Webhooks</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
<div className="u-pull-right">
|
||||
<PluginLink
|
||||
partial
|
||||
query={{ id: 'new' }}
|
||||
disabled={!store.isUserActionAllowed(UserAction.UpdateCustomActions)}
|
||||
>
|
||||
<WithPermissionControl userAction={UserAction.UpdateCustomActions}>
|
||||
<Button variant="primary" icon="plus">
|
||||
Create
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</PluginLink>
|
||||
{() => (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<GTable
|
||||
emptyText={webhooks ? 'No outgoing webhooks found' : 'Loading...'}
|
||||
title={() => (
|
||||
<div className={cx('header')}>
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Outgoing Webhooks</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
<div className="u-pull-right">
|
||||
<PluginLink
|
||||
partial
|
||||
query={{ id: 'new' }}
|
||||
disabled={!store.isUserActionAllowed(UserAction.UpdateCustomActions)}
|
||||
>
|
||||
<WithPermissionControl userAction={UserAction.UpdateCustomActions}>
|
||||
<Button variant="primary" icon="plus">
|
||||
Create
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</PluginLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
data={webhooks}
|
||||
/>
|
||||
</div>
|
||||
{outgoingWebhookIdToEdit && (
|
||||
<OutgoingWebhookForm
|
||||
id={outgoingWebhookIdToEdit}
|
||||
onUpdate={this.update}
|
||||
onHide={this.handleOutgoingWebhookFormHide}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
data={webhooks}
|
||||
/>
|
||||
</div>
|
||||
{outgoingWebhookIdToEdit && (
|
||||
<OutgoingWebhookForm
|
||||
id={outgoingWebhookIdToEdit}
|
||||
onUpdate={this.update}
|
||||
onHide={this.handleOutgoingWebhookFormHide}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -115,144 +115,155 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
return (
|
||||
<PluginPage pageNav={pages['schedule'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper pageName="schedules">
|
||||
<div className={cx('root')}>
|
||||
<VerticalGroup spacing="lg">
|
||||
<div className={cx('header')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<HorizontalGroup>
|
||||
<PluginLink query={{ page: 'schedules' }}>
|
||||
<IconButton style={{ marginTop: '5px' }} name="arrow-left" size="xl" />
|
||||
</PluginLink>
|
||||
<Text.Title editable editModalTitle="Schedule name" level={2} onTextChange={this.handleNameChange}>
|
||||
{schedule?.name}
|
||||
</Text.Title>
|
||||
{schedule && <ScheduleWarning item={schedule} />}
|
||||
</HorizontalGroup>
|
||||
<HorizontalGroup spacing="lg">
|
||||
{users && (
|
||||
{() => (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<VerticalGroup spacing="lg">
|
||||
<div className={cx('header')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<HorizontalGroup>
|
||||
<Text type="secondary">Current timezone:</Text>
|
||||
<UserTimezoneSelect
|
||||
value={currentTimezone}
|
||||
users={users}
|
||||
onChange={this.handleTimezoneChange}
|
||||
/>
|
||||
<PluginLink query={{ page: 'schedules' }}>
|
||||
<IconButton style={{ marginTop: '5px' }} name="arrow-left" size="xl" />
|
||||
</PluginLink>
|
||||
<Text.Title
|
||||
editable
|
||||
editModalTitle="Schedule name"
|
||||
level={2}
|
||||
onTextChange={this.handleNameChange}
|
||||
>
|
||||
{schedule?.name}
|
||||
</Text.Title>
|
||||
{schedule && <ScheduleWarning item={schedule} />}
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<HorizontalGroup>
|
||||
{schedule?.type === ScheduleType.Ical && (
|
||||
<HorizontalGroup spacing="lg">
|
||||
{users && (
|
||||
<HorizontalGroup>
|
||||
<Text type="secondary">Current timezone:</Text>
|
||||
<UserTimezoneSelect
|
||||
value={currentTimezone}
|
||||
users={users}
|
||||
onChange={this.handleTimezoneChange}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" onClick={this.handleExportClick()}>
|
||||
Export
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.handleReloadClick(scheduleId)}>
|
||||
Reload
|
||||
</Button>
|
||||
{schedule?.type === ScheduleType.Ical && (
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" onClick={this.handleExportClick()}>
|
||||
Export
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.handleReloadClick(scheduleId)}>
|
||||
Reload
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<ToolbarButton
|
||||
icon="cog"
|
||||
tooltip="Settings"
|
||||
onClick={() => {
|
||||
this.setState({ showEditForm: true });
|
||||
}}
|
||||
/>
|
||||
<WithConfirm>
|
||||
<ToolbarButton icon="trash-alt" tooltip="Delete" onClick={this.handleDelete} />
|
||||
</WithConfirm>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<ToolbarButton
|
||||
icon="cog"
|
||||
tooltip="Settings"
|
||||
onClick={() => {
|
||||
this.setState({ showEditForm: true });
|
||||
}}
|
||||
/>
|
||||
<WithConfirm>
|
||||
<ToolbarButton icon="trash-alt" tooltip="Delete" onClick={this.handleDelete} />
|
||||
</WithConfirm>
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
{schedule?.type !== ScheduleType.API && (
|
||||
<Text className={cx('desc')} type="secondary">
|
||||
Ical and API/Terraform schedules are read-only
|
||||
</Text>
|
||||
)}
|
||||
<div className={cx('users-timezones')}>
|
||||
<UsersTimezones
|
||||
scheduleId={scheduleId}
|
||||
startMoment={startMoment}
|
||||
onCallNow={schedule?.on_call_now || []}
|
||||
userIds={
|
||||
scheduleStore.relatedUsers[scheduleId] ? Object.keys(scheduleStore.relatedUsers[scheduleId]) : []
|
||||
}
|
||||
tz={currentTimezone}
|
||||
onTzChange={this.handleTimezoneChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={cx('rotations')}>
|
||||
<div className={cx('controls')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" onClick={this.handleTodayClick}>
|
||||
Today
|
||||
</Button>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<Button variant="secondary" onClick={this.handleLeftClick}>
|
||||
<Icon name="angle-left" />
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.handleRightClick}>
|
||||
<Icon name="angle-right" />
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
<Text.Title style={{ marginLeft: '8px' }} level={4} type="primary">
|
||||
{startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')}
|
||||
</Text.Title>
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<ScheduleFinal
|
||||
scheduleId={scheduleId}
|
||||
currentTimezone={currentTimezone}
|
||||
startMoment={startMoment}
|
||||
onClick={this.handleShowForm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Rotations
|
||||
scheduleId={scheduleId}
|
||||
currentTimezone={currentTimezone}
|
||||
startMoment={startMoment}
|
||||
onCreate={this.handleCreateRotation}
|
||||
onUpdate={this.handleUpdateRotation}
|
||||
onDelete={this.handleDeleteRotation}
|
||||
shiftIdToShowRotationForm={shiftIdToShowRotationForm}
|
||||
onShowRotationForm={this.handleShowRotationForm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<ScheduleOverrides
|
||||
scheduleId={scheduleId}
|
||||
currentTimezone={currentTimezone}
|
||||
startMoment={startMoment}
|
||||
onCreate={this.handleCreateOverride}
|
||||
onUpdate={this.handleUpdateOverride}
|
||||
onDelete={this.handleDeleteOverride}
|
||||
shiftIdToShowRotationForm={shiftIdToShowOverridesForm}
|
||||
onShowRotationForm={this.handleShowOverridesForm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{schedule?.type !== ScheduleType.API && (
|
||||
<Text className={cx('desc')} type="secondary">
|
||||
Ical and API/Terraform schedules are read-only
|
||||
</Text>
|
||||
)}
|
||||
<div className={cx('users-timezones')}>
|
||||
<UsersTimezones
|
||||
scheduleId={scheduleId}
|
||||
startMoment={startMoment}
|
||||
onCallNow={schedule?.on_call_now || []}
|
||||
userIds={
|
||||
scheduleStore.relatedUsers[scheduleId]
|
||||
? Object.keys(scheduleStore.relatedUsers[scheduleId])
|
||||
: []
|
||||
}
|
||||
tz={currentTimezone}
|
||||
onTzChange={this.handleTimezoneChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={cx('rotations')}>
|
||||
<div className={cx('controls')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" onClick={this.handleTodayClick}>
|
||||
Today
|
||||
</Button>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<Button variant="secondary" onClick={this.handleLeftClick}>
|
||||
<Icon name="angle-left" />
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.handleRightClick}>
|
||||
<Icon name="angle-right" />
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
<Text.Title style={{ marginLeft: '8px' }} level={4} type="primary">
|
||||
{startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')}
|
||||
</Text.Title>
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<ScheduleFinal
|
||||
scheduleId={scheduleId}
|
||||
currentTimezone={currentTimezone}
|
||||
startMoment={startMoment}
|
||||
onClick={this.handleShowForm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Rotations
|
||||
scheduleId={scheduleId}
|
||||
currentTimezone={currentTimezone}
|
||||
startMoment={startMoment}
|
||||
onCreate={this.handleCreateRotation}
|
||||
onUpdate={this.handleUpdateRotation}
|
||||
onDelete={this.handleDeleteRotation}
|
||||
shiftIdToShowRotationForm={shiftIdToShowRotationForm}
|
||||
onShowRotationForm={this.handleShowRotationForm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<ScheduleOverrides
|
||||
scheduleId={scheduleId}
|
||||
currentTimezone={currentTimezone}
|
||||
startMoment={startMoment}
|
||||
onCreate={this.handleCreateOverride}
|
||||
onUpdate={this.handleUpdateOverride}
|
||||
onDelete={this.handleDeleteOverride}
|
||||
shiftIdToShowRotationForm={shiftIdToShowOverridesForm}
|
||||
onShowRotationForm={this.handleShowOverridesForm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
{showEditForm && (
|
||||
<ScheduleForm
|
||||
id={schedule.id}
|
||||
onUpdate={this.update}
|
||||
onHide={() => {
|
||||
this.setState({ showEditForm: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showScheduleICalSettings && (
|
||||
<Modal
|
||||
isOpen
|
||||
title="Schedule export"
|
||||
closeOnEscape
|
||||
onDismiss={() => this.setState({ showScheduleICalSettings: false })}
|
||||
>
|
||||
<ScheduleICalSettings id={scheduleId} />
|
||||
</Modal>
|
||||
{showEditForm && (
|
||||
<ScheduleForm
|
||||
id={schedule.id}
|
||||
onUpdate={this.update}
|
||||
onHide={() => {
|
||||
this.setState({ showEditForm: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showScheduleICalSettings && (
|
||||
<Modal
|
||||
isOpen
|
||||
title="Schedule export"
|
||||
closeOnEscape
|
||||
onDismiss={() => this.setState({ showScheduleICalSettings: false })}
|
||||
>
|
||||
<ScheduleICalSettings id={scheduleId} />
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
|
|
|
|||
|
|
@ -180,74 +180,76 @@ class Users extends React.Component<UsersProps, UsersState> {
|
|||
pageName="users"
|
||||
itemNotFoundMessage={`User with id=${query?.id} is not found. Please select user from the list.`}
|
||||
>
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('root', 'TEST-users-page')}>
|
||||
<div className={cx('users-header')}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline' }}>
|
||||
<div>
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Users</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
<Text type="secondary">
|
||||
To manage permissions or add users, please visit{' '}
|
||||
<a href="/org/users">Grafana user management</a>
|
||||
</Text>
|
||||
{() => (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('root', 'TEST-users-page')}>
|
||||
<div className={cx('users-header')}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline' }}>
|
||||
<div>
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Users</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
<Text type="secondary">
|
||||
To manage permissions or add users, please visit{' '}
|
||||
<a href="/org/users">Grafana user management</a>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PluginLink partial query={{ id: 'me' }}>
|
||||
<Button variant="primary" icon="user">
|
||||
View my profile
|
||||
</Button>
|
||||
</PluginLink>
|
||||
</div>
|
||||
{store.isUserActionAllowed(UserAction.ViewOtherUsers) ? (
|
||||
<>
|
||||
<div className={cx('user-filters-container')}>
|
||||
<UsersFilters
|
||||
className={cx('users-filters')}
|
||||
value={usersFilters}
|
||||
onChange={this.handleUsersFiltersChange}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="times"
|
||||
onClick={handleClear}
|
||||
className={cx('searchIntegrationClear')}
|
||||
>
|
||||
Clear filters
|
||||
<PluginLink partial query={{ id: 'me' }}>
|
||||
<Button variant="primary" icon="user">
|
||||
View my profile
|
||||
</Button>
|
||||
</div>
|
||||
</PluginLink>
|
||||
</div>
|
||||
{store.isUserActionAllowed(UserAction.ViewOtherUsers) ? (
|
||||
<>
|
||||
<div className={cx('user-filters-container')}>
|
||||
<UsersFilters
|
||||
className={cx('users-filters')}
|
||||
value={usersFilters}
|
||||
onChange={this.handleUsersFiltersChange}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="times"
|
||||
onClick={handleClear}
|
||||
className={cx('searchIntegrationClear')}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<GTable
|
||||
emptyText={results ? 'No users found' : 'Loading...'}
|
||||
rowKey="pk"
|
||||
data={results}
|
||||
columns={columns}
|
||||
rowClassName={getUserRowClassNameFn(userPkToEdit, userStore.currentUserPk)}
|
||||
pagination={{
|
||||
page,
|
||||
total: Math.ceil((count || 0) / ITEMS_PER_PAGE),
|
||||
onChange: this.handleChangePage,
|
||||
}}
|
||||
<GTable
|
||||
emptyText={results ? 'No users found' : 'Loading...'}
|
||||
rowKey="pk"
|
||||
data={results}
|
||||
columns={columns}
|
||||
rowClassName={getUserRowClassNameFn(userPkToEdit, userStore.currentUserPk)}
|
||||
pagination={{
|
||||
page,
|
||||
total: Math.ceil((count || 0) / ITEMS_PER_PAGE),
|
||||
onChange: this.handleChangePage,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Alert
|
||||
/* @ts-ignore */
|
||||
title={
|
||||
<>
|
||||
You don't have enough permissions to view other users because you are not Admin.{' '}
|
||||
<PluginLink query={{ page: 'users', id: 'me' }}>Click here</PluginLink> to open your profile
|
||||
</>
|
||||
}
|
||||
severity="info"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Alert
|
||||
/* @ts-ignore */
|
||||
title={
|
||||
<>
|
||||
You don't have enough permissions to view other users because you are not Admin.{' '}
|
||||
<PluginLink query={{ page: 'users', id: 'me' }}>Click here</PluginLink> to open your profile
|
||||
</>
|
||||
}
|
||||
severity="info"
|
||||
/>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
{userPkToEdit && <UserSettings id={userPkToEdit} onHide={this.handleHideUserSettings} />}
|
||||
</div>
|
||||
{userPkToEdit && <UserSettings id={userPkToEdit} onHide={this.handleHideUserSettings} />}
|
||||
</div>
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue