Move alerts inside PageNav (#1040)
# What this PR does Fix for #904 - TopNav: Move Alerts inside PluginPage Fix for #908 - Horizontal menu scrolling fix for tabs There's also a few styling tweaks to be more in match with Grafana core styles.
This commit is contained in:
parent
a1e4f72280
commit
d8dcb673da
26 changed files with 689 additions and 690 deletions
|
|
@ -6,25 +6,34 @@ import Header from 'navbar/Header/Header';
|
|||
import { pages } from 'pages';
|
||||
import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { DEFAULT_PAGE } from 'utils/consts';
|
||||
import { useQueryParams } from 'utils/hooks';
|
||||
|
||||
export const PluginPage = (isTopNavbar() ? RealPlugin : PluginPageFallback) as React.ComponentType<PluginPageProps>;
|
||||
export const PluginPage = (
|
||||
isTopNavbar() ? RealPlugin : PluginPageFallback
|
||||
) as React.ComponentType<ExtendedPluginPageProps>;
|
||||
|
||||
function RealPlugin(props: PluginPageProps): React.ReactNode {
|
||||
interface ExtendedPluginPageProps extends PluginPageProps {
|
||||
renderAlertsFn?: () => React.ReactNode;
|
||||
}
|
||||
|
||||
function RealPlugin(props: ExtendedPluginPageProps): React.ReactNode {
|
||||
const store = useStore();
|
||||
|
||||
const queryParams = useQueryParams();
|
||||
const page = queryParams.get('page');
|
||||
const page = queryParams.get('page') || DEFAULT_PAGE;
|
||||
|
||||
return (
|
||||
<RealPluginPage {...props}>
|
||||
{/* Render alerts at the top */}
|
||||
{props.renderAlertsFn && props.renderAlertsFn()}
|
||||
<Header page={page} backendLicense={store.backendLicense} />
|
||||
<h3 className="page-title">{pages[page].text}</h3>
|
||||
{pages[page].text && <h3 className="page-title">{pages[page].text}</h3>}
|
||||
{props.children}
|
||||
</RealPluginPage>
|
||||
);
|
||||
}
|
||||
|
||||
function PluginPageFallback(props: PluginPageProps): React.ReactNode {
|
||||
function PluginPageFallback(props: ExtendedPluginPageProps): React.ReactNode {
|
||||
return props.children;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
.root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerts_horizontal {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
.alerts-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24px;
|
||||
gap: 10px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin: 24px 0;
|
||||
.navbar-legacy .alerts-container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar-legacy {
|
||||
padding-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
import plugin from '../../../package.json'; // eslint-disable-line
|
||||
import React, { FC, useEffect, useState, useCallback } from 'react';
|
||||
|
||||
import { Alert } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import { AppRootProps } from 'types';
|
||||
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import { getIfChatOpsConnected } from 'containers/DefaultPageLayout/helper';
|
||||
import { pages } from 'pages';
|
||||
import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
|
||||
import { AppFeature } from 'state/features';
|
||||
import { useStore } from 'state/useStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
import { isUserActionAllowed, UserActions } from 'utils/authorization';
|
||||
import { GRAFANA_LICENSE_OSS } from 'utils/consts';
|
||||
import { useForceUpdate } from 'utils/hooks';
|
||||
import { DEFAULT_PAGE, GRAFANA_LICENSE_OSS } from 'utils/consts';
|
||||
import { useForceUpdate, useQueryParams } from 'utils/hooks';
|
||||
|
||||
import plugin from '../../../package.json'; // eslint-disable-line
|
||||
|
||||
import { getItem, setItem } from 'utils/localStorage';
|
||||
import sanitize from 'utils/sanitize';
|
||||
|
||||
|
|
@ -33,10 +38,12 @@ enum AlertID {
|
|||
|
||||
const DefaultPageLayout: FC<DefaultPageLayoutProps> = observer((props) => {
|
||||
const { children, query } = props;
|
||||
const queryParams = useQueryParams();
|
||||
|
||||
const [showSlackInstallAlert, setShowSlackInstallAlert] = useState<SlackError | undefined>();
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
const page = queryParams.get('page') || DEFAULT_PAGE;
|
||||
|
||||
const handleCloseInstallSlackAlert = useCallback(() => {
|
||||
setShowSlackInstallAlert(undefined);
|
||||
|
|
@ -68,15 +75,41 @@ const DefaultPageLayout: FC<DefaultPageLayoutProps> = observer((props) => {
|
|||
const isChatOpsConnected = getIfChatOpsConnected(currentUser);
|
||||
const isPhoneVerified = currentUser?.cloud_connection_status === 3 || currentUser?.verified_phone_number;
|
||||
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
<div className={styles.alerts_horizontal}>
|
||||
if (isTopNavbar()) {
|
||||
return renderTopNavbar();
|
||||
}
|
||||
|
||||
return renderLegacyNavbar();
|
||||
|
||||
function renderTopNavbar(): JSX.Element {
|
||||
return (
|
||||
<PluginPage pageNav={pages[page].getPageNav()} renderAlertsFn={renderAlertsFn}>
|
||||
<div className={cx('root')}>{children}</div>
|
||||
</PluginPage>
|
||||
);
|
||||
}
|
||||
|
||||
function renderLegacyNavbar(): JSX.Element {
|
||||
return (
|
||||
<PluginPage>
|
||||
<div className="page-container u-height-100">
|
||||
<div className={cx('root', 'navbar-legacy')}>
|
||||
{renderAlertsFn()}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</PluginPage>
|
||||
);
|
||||
}
|
||||
|
||||
function renderAlertsFn(): JSX.Element {
|
||||
return (
|
||||
<div className={cx('alerts-container')}>
|
||||
{showSlackInstallAlert && (
|
||||
<Alert
|
||||
className={styles.alert}
|
||||
className={cx('alert')}
|
||||
onRemove={handleCloseInstallSlackAlert}
|
||||
severity="warning"
|
||||
// @ts-ignore
|
||||
title="Slack integration warning"
|
||||
>
|
||||
{getSlackMessage(
|
||||
|
|
@ -88,7 +121,7 @@ const DefaultPageLayout: FC<DefaultPageLayoutProps> = observer((props) => {
|
|||
)}
|
||||
{currentTeam?.banner.title != null && !getItem(currentTeam?.banner.title) && (
|
||||
<Alert
|
||||
className={styles.alert}
|
||||
className={cx('alert')}
|
||||
severity="success"
|
||||
title={currentTeam.banner.title}
|
||||
onRemove={getRemoveAlertHandler(currentTeam?.banner.title)}
|
||||
|
|
@ -106,7 +139,7 @@ const DefaultPageLayout: FC<DefaultPageLayoutProps> = observer((props) => {
|
|||
store.backendVersion !== plugin?.version &&
|
||||
!getItem(`version_mismatch_${store.backendVersion}_${plugin?.version}`) && (
|
||||
<Alert
|
||||
className={styles.alert}
|
||||
className={cx('alert')}
|
||||
severity="warning"
|
||||
title={'Version mismatch!'}
|
||||
onRemove={getRemoveAlertHandler(`version_mismatch_${store.backendVersion}_${plugin?.version}`)}
|
||||
|
|
@ -137,7 +170,7 @@ const DefaultPageLayout: FC<DefaultPageLayoutProps> = observer((props) => {
|
|||
) && (
|
||||
<Alert
|
||||
onRemove={getRemoveAlertHandler(AlertID.CONNECTIVITY_WARNING)}
|
||||
className={styles.alert}
|
||||
className={cx('alert')}
|
||||
severity="warning"
|
||||
// @ts-ignore
|
||||
title="Connectivity Warning"
|
||||
|
|
@ -160,9 +193,8 @@ const DefaultPageLayout: FC<DefaultPageLayoutProps> = observer((props) => {
|
|||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default DefaultPageLayout;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,17 @@
|
|||
.teamSelect {
|
||||
width: 200px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
padding: 16px 0;
|
||||
margin-right: 24px;
|
||||
|
||||
&--topRight {
|
||||
right: 14px;
|
||||
top: 12px;
|
||||
}
|
||||
&--topRightIncident {
|
||||
right: 32px;
|
||||
top: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.teamSelectLabel {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.teamSelectText,
|
||||
.teamSelectLink {
|
||||
line-height: 1.25;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.teamSelectLink {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,13 +49,15 @@ const GrafanaTeamSelect = observer((props: GrafanaTeamSelectProps) => {
|
|||
};
|
||||
|
||||
const content = (
|
||||
<div className={cx('teamSelect', { 'teamSelect--topRight': isTopNavbar() })}>
|
||||
<div className={cx('teamSelect')}>
|
||||
<div className={cx('teamSelectLabel')}>
|
||||
<Label>
|
||||
Select Team{' '}
|
||||
<Tooltip content="The objects on this page are filtered by team and you can only view the objects that belong to your team. Note that filtering within Grafana OnCall is meant for usability, not access management.">
|
||||
<Icon name="info-circle" size="md" className={cx('teamSelectInfo')}></Icon>
|
||||
</Tooltip>
|
||||
<span className={cx('teamSelectText')}>
|
||||
Select Team{''}
|
||||
<Tooltip content="The objects on this page are filtered by team and you can only view the objects that belong to your team. Note that filtering within Grafana OnCall is meant for usability, not access management.">
|
||||
<Icon name="info-circle" size="md" className={cx('teamSelectInfo')}></Icon>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Label>
|
||||
<WithPermissionControl userAction={UserActions.TeamsWrite}>
|
||||
<PluginLink path="/org/teams" className={cx('teamSelectLink')}>
|
||||
|
|
|
|||
|
|
@ -4,24 +4,12 @@
|
|||
max-width: unset !important;
|
||||
}
|
||||
|
||||
.oncall-header {
|
||||
padding-top: 0;
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
|
||||
.scrollbar-view h1:first-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.page-container.page-body {
|
||||
flex-grow: 1 !important;
|
||||
[class$='-page-header'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
max-width: unset !important;
|
||||
flex-grow: unset !important;
|
||||
flex-basis: unset !important;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.page-scrollbar-content > div:first-child {
|
||||
|
|
@ -34,34 +22,6 @@
|
|||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* This is for Grafana 8, remove later */
|
||||
@media (max-width: 1540px) {
|
||||
.page-header__tabs > ul > li > a > div {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1540px) {
|
||||
.page-header__tabs > div > div > a > div {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.sidemenu {
|
||||
position: fixed !important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-view {
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.page-header__tabs li a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header__info-block {
|
||||
flex-grow: 1; /* Stretch the navigation subtitle panel */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.header-topnavbar {
|
||||
padding-top: 0;
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
|
||||
.navbar-heading {
|
||||
padding: 4px;
|
||||
margin: 0 0 0 8px;
|
||||
|
|
@ -16,3 +21,8 @@
|
|||
align-items: center;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Card } from '@grafana/ui';
|
||||
import classnames from 'classnames';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import gitHubStarSVG from 'assets/img/github_star.svg';
|
||||
|
|
@ -16,15 +15,16 @@ const cx = cn.bind(styles);
|
|||
|
||||
export default function Header({ page, backendLicense }: { page: string; backendLicense: string }) {
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="page-header">
|
||||
<div className={classnames('page-header__inner', { 'oncall-header': isTopNavbar() })}>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('page-header__inner', { 'header-topnavbar': isTopNavbar() })}>
|
||||
<div className={cx('navbar-left')}>
|
||||
<span className="page-header__logo">
|
||||
<img className="page-header__img" src={logo} alt="Grafana OnCall" />
|
||||
</span>
|
||||
|
||||
<div className="page-header__info-block">{renderHeading()}</div>
|
||||
|
||||
</div>
|
||||
<div className={cx('navbar-right')}>
|
||||
<GrafanaTeamSelect currentPage={page} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
.root {
|
||||
min-width: 1500px;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,18 @@ export default function LegacyNavTabsBar({ currentPage }: { currentPage: string
|
|||
.filter((page) => (page.hideFromTabsFn ? !page.hideFromTabsFn(store) : !page.hideFromTabs));
|
||||
|
||||
return (
|
||||
<TabsBar className={cx('root')}>
|
||||
{navigationPages.map((page, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
icon={page.icon as IconName}
|
||||
label={page.text}
|
||||
href={page.path}
|
||||
active={currentPage === page.id}
|
||||
/>
|
||||
))}
|
||||
</TabsBar>
|
||||
<div className={cx('root')}>
|
||||
<TabsBar>
|
||||
{navigationPages.map((page, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
icon={page.icon as IconName}
|
||||
label={page.text}
|
||||
href={page.path}
|
||||
active={currentPage === page.id}
|
||||
/>
|
||||
))}
|
||||
</TabsBar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Icon, IconButton, LoadingPlaceholder, Tooltip, VerticalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -25,7 +24,6 @@ import EscalationChainForm from 'containers/EscalationChainForm/EscalationChainF
|
|||
import EscalationChainSteps from 'containers/EscalationChainSteps/EscalationChainSteps';
|
||||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { EscalationChain } from 'models/escalation_chain/escalation_chain.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
|
|
@ -135,90 +133,88 @@ class EscalationChainsPage extends React.Component<EscalationChainsPageProps, Es
|
|||
const searchResult = escalationChainStore.getSearchResult(escalationChainsFilters.searchTerm);
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['escalations'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="escalation"
|
||||
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} />
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="escalation"
|
||||
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={UserActions.IntegrationsWrite}>
|
||||
<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>
|
||||
{!searchResult || searchResult.length ? (
|
||||
<div className={cx('escalations')}>
|
||||
<div className={cx('left-column')}>
|
||||
<WithPermissionControl userAction={UserActions.IntegrationsWrite}>
|
||||
) : (
|
||||
<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={UserActions.EscalationChainsWrite}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
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>
|
||||
) : (
|
||||
<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={UserActions.EscalationChainsWrite}>
|
||||
<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}
|
||||
</VerticalGroup>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
</div>
|
||||
{showCreateEscalationChainModal && (
|
||||
<EscalationChainForm
|
||||
escalationChainId={escalationChainIdToCopy}
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
showCreateEscalationChainModal: false,
|
||||
escalationChainIdToCopy: undefined,
|
||||
});
|
||||
}}
|
||||
onUpdate={this.handleEscalationChainCreate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.block {
|
||||
padding: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.payload-subtitle {
|
||||
margin-bottom: var(--title-marginBottom);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
Modal,
|
||||
Tooltip,
|
||||
} from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment-timezone';
|
||||
|
|
@ -46,7 +45,6 @@ import {
|
|||
GroupedAlert,
|
||||
} from 'models/alertgroup/alertgroup.types';
|
||||
import { ResolutionNoteSourceTypesToDisplayName } from 'models/resolution_note/resolution_note.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
|
@ -127,71 +125,69 @@ 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>
|
||||
<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>
|
||||
{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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +206,7 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
|
|||
const showLinkTo = !incident.dependent_alert_groups.length && !incident.root_alert_group && !incident.resolved;
|
||||
|
||||
return (
|
||||
<Block withBackground>
|
||||
<Block withBackground className={cx('block')}>
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup className={cx('title')}>
|
||||
<PluginLink query={{ page: 'incidents', cursor, start, perpage }}>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { ReactElement, SyntheticEvent } from 'react';
|
||||
|
||||
import { Button, Icon, Tooltip, VerticalGroup, LoadingPlaceholder, HorizontalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { get } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -20,7 +19,6 @@ import IncidentsFilters from 'containers/IncidentsFilters/IncidentsFilters';
|
|||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { Alert, Alert as AlertType, AlertAction } from 'models/alertgroup/alertgroup.types';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { pages } from 'pages';
|
||||
import { getActionButtons, getIncidentStatusTag, renderRelatedUsers } from 'pages/incident/Incident.helpers';
|
||||
import { move } from 'state/helpers';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
|
|
@ -101,12 +99,10 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
|
|||
|
||||
render() {
|
||||
return (
|
||||
<PluginPage pageNav={pages['incidents'].getPageNav()}>
|
||||
<div className={cx('root')}>
|
||||
{this.renderIncidentFilters()}
|
||||
{this.renderTable()}
|
||||
</div>
|
||||
</PluginPage>
|
||||
<div className={cx('root')}>
|
||||
{this.renderIncidentFilters()}
|
||||
{this.renderTable()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, LoadingPlaceholder, VerticalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -24,7 +23,6 @@ import { IntegrationSettingsTab } from 'containers/IntegrationSettings/Integrati
|
|||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel';
|
||||
import { AlertReceiveChannelOption } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
|
|
@ -131,121 +129,119 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
const searchResult = alertReceiveChannelStore.getSearchResult();
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['integrations'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="integration"
|
||||
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} />
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="integration"
|
||||
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={UserActions.IntegrationsWrite}>
|
||||
<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>
|
||||
{searchResult?.length ? (
|
||||
<div className={cx('integrations')}>
|
||||
<div className={cx('integrationsList')}>
|
||||
) : 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={UserActions.IntegrationsWrite}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
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>
|
||||
) : 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={UserActions.IntegrationsWrite}>
|
||||
<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');
|
||||
}}
|
||||
</VerticalGroup>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<LoadingPlaceholder text="Loading..." />
|
||||
)}
|
||||
{showCreateIntegrationModal && (
|
||||
<CreateAlertReceiveChannelContainer
|
||||
onHide={() => {
|
||||
this.setState({ showCreateIntegrationModal: false });
|
||||
}}
|
||||
onCreate={this.handleCreateNewAlertReceiveChannel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
</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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, VerticalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment-timezone';
|
||||
|
|
@ -16,7 +15,6 @@ import { WithPermissionControl } from 'containers/WithPermissionControl/WithPerm
|
|||
import { getAlertReceiveChannelDisplayName } from 'models/alert_receive_channel/alert_receive_channel.helpers';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { Maintenance, MaintenanceMode, MaintenanceType } from 'models/maintenance/maintenance.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import { UserActions } from 'utils/authorization';
|
||||
|
|
@ -117,7 +115,7 @@ class MaintenancePage extends React.Component<MaintenancePageProps, MaintenanceP
|
|||
];
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['maintenance'].getPageNav()}>
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<GTable
|
||||
emptyText={data ? 'No maintenances found' : 'Loading...'}
|
||||
|
|
@ -160,7 +158,7 @@ class MaintenancePage extends React.Component<MaintenancePageProps, MaintenanceP
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
</PluginPage>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Tag, Tooltip } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -96,41 +95,39 @@ class OrganizationLogPage extends React.Component<OrganizationLogProps, Organiza
|
|||
const loading = !results;
|
||||
|
||||
return (
|
||||
<PluginPage>
|
||||
<div className={cx('root')}>
|
||||
<OrganizationLogFilters value={filters} onChange={this.handleChangeOrganizationLogFilters} />
|
||||
<GTable
|
||||
rowKey="id"
|
||||
title={() => (
|
||||
<div className={cx('header')}>
|
||||
<Text.Title className={cx('users-title')} level={3}>
|
||||
Organization Logs
|
||||
</Text.Title>
|
||||
<Button onClick={this.refresh} icon={loading ? 'fa fa-spinner' : 'sync'}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
showHeader={true}
|
||||
data={results}
|
||||
loading={loading}
|
||||
emptyText={results ? 'No logs found' : 'Loading...'}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
page,
|
||||
total: Math.ceil((total || 0) / ITEMS_PER_PAGE),
|
||||
onChange: this.handleChangePage,
|
||||
}}
|
||||
rowClassName={cx('align-top')}
|
||||
expandable={{
|
||||
expandedRowRender: this.renderFullDescription,
|
||||
expandRowByClick: true,
|
||||
expandedRowKeys: expandedLogsKeys,
|
||||
onExpandedRowsChange: this.handleExpandedRowsChange,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</PluginPage>
|
||||
<div className={cx('root')}>
|
||||
<OrganizationLogFilters value={filters} onChange={this.handleChangeOrganizationLogFilters} />
|
||||
<GTable
|
||||
rowKey="id"
|
||||
title={() => (
|
||||
<div className={cx('header')}>
|
||||
<Text.Title className={cx('users-title')} level={3}>
|
||||
Organization Logs
|
||||
</Text.Title>
|
||||
<Button onClick={this.refresh} icon={loading ? 'fa fa-spinner' : 'sync'}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
showHeader={true}
|
||||
data={results}
|
||||
loading={loading}
|
||||
emptyText={results ? 'No logs found' : 'Loading...'}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
page,
|
||||
total: Math.ceil((total || 0) / ITEMS_PER_PAGE),
|
||||
onChange: this.handleChangePage,
|
||||
}}
|
||||
rowClassName={cx('align-top')}
|
||||
expandable={{
|
||||
expandedRowRender: this.renderFullDescription,
|
||||
expandRowByClick: true,
|
||||
expandedRowKeys: expandedLogsKeys,
|
||||
onExpandedRowsChange: this.handleExpandedRowsChange,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, HorizontalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import LegacyNavHeading from 'navbar/LegacyNavHeading';
|
||||
|
|
@ -19,7 +18,6 @@ import OutgoingWebhookForm from 'containers/OutgoingWebhookForm/OutgoingWebhookF
|
|||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { ActionDTO } from 'models/action';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
|
|
@ -111,54 +109,52 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
];
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['outgoing_webhooks'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="outgoing webhook"
|
||||
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={!isUserActionAllowed(UserActions.OutgoingWebhooksWrite)}
|
||||
>
|
||||
<WithPermissionControl userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button variant="primary" icon="plus">
|
||||
Create
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</PluginLink>
|
||||
</div>
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="outgoing webhook"
|
||||
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={!isUserActionAllowed(UserActions.OutgoingWebhooksWrite)}
|
||||
>
|
||||
<WithPermissionControl userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button variant="primary" icon="plus">
|
||||
Create
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</PluginLink>
|
||||
</div>
|
||||
)}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
data={webhooks}
|
||||
/>
|
||||
</div>
|
||||
{outgoingWebhookIdToEdit && (
|
||||
<OutgoingWebhookForm
|
||||
id={outgoingWebhookIdToEdit}
|
||||
onUpdate={this.update}
|
||||
onHide={this.handleOutgoingWebhookFormHide}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
</div>
|
||||
)}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
data={webhooks}
|
||||
/>
|
||||
</div>
|
||||
{outgoingWebhookIdToEdit && (
|
||||
<OutgoingWebhookForm
|
||||
id={outgoingWebhookIdToEdit}
|
||||
onUpdate={this.update}
|
||||
onHide={this.handleOutgoingWebhookFormHide}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon, Modal } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -20,7 +19,6 @@ import ScheduleICalSettings from 'containers/ScheduleIcalLink/ScheduleIcalLink';
|
|||
import UsersTimezones from 'containers/UsersTimezones/UsersTimezones';
|
||||
import { Schedule, ScheduleType, Shift } from 'models/schedule/schedule.types';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
|
|
@ -112,156 +110,154 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
shiftIdToShowOverridesForm;
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['schedule'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper pageName="schedules">
|
||||
{() => (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<VerticalGroup spacing="lg">
|
||||
<div className={cx('header')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<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 && (
|
||||
<HorizontalGroup>
|
||||
<Text type="secondary">Current timezone:</Text>
|
||||
<UserTimezoneSelect
|
||||
value={currentTimezone}
|
||||
users={users}
|
||||
onChange={this.handleTimezoneChange}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<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 && (
|
||||
<HorizontalGroup>
|
||||
<Text type="secondary">Current timezone:</Text>
|
||||
<UserTimezoneSelect
|
||||
value={currentTimezone}
|
||||
users={users}
|
||||
onChange={this.handleTimezoneChange}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<HorizontalGroup>
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" onClick={this.handleExportClick()}>
|
||||
Export
|
||||
</Button>
|
||||
|
||||
{(schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar) && (
|
||||
<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>
|
||||
|
||||
{(schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar) && (
|
||||
<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>
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
{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>
|
||||
<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>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, IconButton, LoadingPlaceholder, VerticalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
|
@ -25,7 +24,6 @@ import { WithPermissionControl } from 'containers/WithPermissionControl/WithPerm
|
|||
import { Schedule, ScheduleType } from 'models/schedule/schedule.types';
|
||||
import { getSlackChannelName } from 'models/slack_channel/slack_channel.helpers';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { pages } from 'pages';
|
||||
import { getStartOfWeek } from 'pages/schedule/Schedule.helpers';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
|
@ -135,7 +133,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
: undefined;
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['schedules'].getPageNav()}>
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup justify="space-between">
|
||||
|
|
@ -192,7 +190,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
</PluginPage>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Tab, TabsBar } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
|
|
@ -35,11 +34,7 @@ class SettingsPage extends React.Component<SettingsPageProps, SettingsPageState>
|
|||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PluginPage pageNav={this.getMatchingPageNav()}>
|
||||
<div className={cx('root')}>{this.renderContent()}</div>
|
||||
</PluginPage>
|
||||
);
|
||||
return <div className={cx('root')}>{this.renderContent()}</div>;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
|
|
@ -17,13 +16,11 @@ const cx = cn.bind(styles);
|
|||
class Test extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<PluginPage>
|
||||
<div className={cx('root')}>
|
||||
<WithPermissionControl userAction={UserActions.SchedulesWrite}>
|
||||
{(disabled) => <Button disabled={disabled}>Click me!</Button>}
|
||||
</WithPermissionControl>
|
||||
</div>
|
||||
</PluginPage>
|
||||
<div className={cx('root')}>
|
||||
<WithPermissionControl userAction={UserActions.SchedulesWrite}>
|
||||
{(disabled) => <Button disabled={disabled}>Click me!</Button>}
|
||||
</WithPermissionControl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Alert, Button, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui';
|
||||
import { PluginPage } from 'PluginPage';
|
||||
import cn from 'classnames/bind';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -20,7 +19,6 @@ import UsersFilters from 'components/UsersFilters/UsersFilters';
|
|||
import UserSettings from 'containers/UserSettings/UserSettings';
|
||||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { User as UserType } from 'models/user/user.types';
|
||||
import { pages } from 'pages';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
|
|
@ -159,85 +157,83 @@ class Users extends React.Component<UsersProps, UsersState> {
|
|||
const { count, results } = userStore.getSearchResult();
|
||||
|
||||
return (
|
||||
<PluginPage pageNav={pages['users'].getPageNav()}>
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="user"
|
||||
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>
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="user"
|
||||
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>
|
||||
<PluginLink partial query={{ id: 'me' }}>
|
||||
<Button variant="primary" icon="user">
|
||||
View my profile
|
||||
</Button>
|
||||
</PluginLink>
|
||||
</div>
|
||||
{isUserActionAllowed(UserActions.UserSettingsRead) ? (
|
||||
<>
|
||||
<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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<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"
|
||||
/>
|
||||
)}
|
||||
<PluginLink partial query={{ id: 'me' }}>
|
||||
<Button variant="primary" icon="user">
|
||||
View my profile
|
||||
</Button>
|
||||
</PluginLink>
|
||||
</div>
|
||||
{userPkToEdit && <UserSettings id={userPkToEdit} onHide={this.handleHideUserSettings} />}
|
||||
{isUserActionAllowed(UserActions.UserSettingsRead) ? (
|
||||
<>
|
||||
<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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
</PluginPage>
|
||||
{userPkToEdit && <UserSettings id={userPkToEdit} onHide={this.handleHideUserSettings} />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { routes } from 'pages/routes';
|
|||
import { rootStore } from 'state';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { isUserActionAllowed } from 'utils/authorization';
|
||||
import { DEFAULT_PAGE } from 'utils/consts';
|
||||
import { useQueryParams, useQueryPath } from 'utils/hooks';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
|
@ -49,7 +50,7 @@ export const GrafanaPluginRootPage = (props: AppRootProps) => (
|
|||
export const Root = observer((props: AppRootProps) => {
|
||||
const [didFinishLoading, setDidFinishLoading] = useState(false);
|
||||
const queryParams = useQueryParams();
|
||||
const page = queryParams.get('page');
|
||||
const page = queryParams.get('page') || DEFAULT_PAGE;
|
||||
const path = useQueryPath();
|
||||
|
||||
// Required to support grafana instances that use a custom `root_url`.
|
||||
|
|
@ -93,18 +94,15 @@ export const Root = observer((props: AppRootProps) => {
|
|||
{!isTopNavbar() && (
|
||||
<>
|
||||
<Header page={page} backendLicense={store.backendLicense} />
|
||||
<nav className="page-container">
|
||||
<LegacyNavTabsBar currentPage={page} />
|
||||
</nav>
|
||||
<LegacyNavTabsBar currentPage={page} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={classnames(
|
||||
{ 'page-container': !isTopNavbar() },
|
||||
{ 'page-body': !isTopNavbar() },
|
||||
'u-position-relative'
|
||||
)}
|
||||
className={classnames('u-position-relative', 'u-flex-grow-1', {
|
||||
'u-overflow-x-auto': !isTopNavbar(),
|
||||
'page-body': !isTopNavbar(),
|
||||
})}
|
||||
>
|
||||
{userHasAccess ? (
|
||||
<Page {...props} query={...getQueryParams()} path={pathWithoutLeadingSlash} store={store} />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.u-overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.u-pull-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
@ -18,6 +22,10 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.u-height-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.u-flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -28,6 +36,10 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.u-flex-grow-1 {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.u-align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,3 +7,5 @@ export const GRAFANA_LICENSE_OSS = 'OpenSource';
|
|||
|
||||
// Reusable breakpoint sizes
|
||||
export const BREAKPOINT_TABS = 1024;
|
||||
|
||||
export const DEFAULT_PAGE = 'incidents';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue