add status pages

Signed-off-by: Marko Simon <marko@ilert.com>
This commit is contained in:
Marko Simon
2022-10-04 11:42:39 +02:00
parent 3ce7fb36d4
commit 21d83eb2cc
19 changed files with 856 additions and 120 deletions
+33
View File
@@ -29,6 +29,7 @@ import {
OnCall,
Schedule,
Service,
StatusPage,
UptimeMonitor,
User,
} from '../types';
@@ -37,6 +38,7 @@ import {
GetAlertsCountOpts,
GetAlertsOpts,
GetServicesOpts,
GetStatusPagesOpts,
ILertApi,
ServiceRequest,
} from './types';
@@ -519,6 +521,27 @@ export class ILertClient implements ILertApi {
return response;
}
async fetchStatusPages(opts?: GetStatusPagesOpts): Promise<StatusPage[]> {
const init = {
headers: JSON_HEADERS,
};
const query = new URLSearchParams();
if (opts?.maxResults !== undefined) {
query.append('max-results', String(opts.maxResults));
}
if (opts?.startIndex !== undefined) {
query.append('start-index', String(opts.startIndex));
}
query.append('include', 'subscribed');
const response = await this.fetch(
`/api/status-pages?${query.toString()}`,
init,
);
return response;
}
getAlertDetailsURL(alert: Alert): string {
return `${this.baseUrl}/alert/view.jsf?id=${encodeURIComponent(alert.id)}`;
}
@@ -556,6 +579,16 @@ export class ILertClient implements ILertApi {
)}`;
}
getStatusPageDetailsURL(statusPage: StatusPage): string {
return `${this.baseUrl}/status-page/view.jsf?id=${encodeURIComponent(
statusPage.id,
)}`;
}
getStatusPageURL(statusPage: StatusPage): string {
return statusPage.domain ? statusPage.domain : statusPage.subdomain;
}
getUserPhoneNumber(user: User | null) {
return user?.mobile?.number || user?.landline?.number || '';
}
+2 -1
View File
@@ -16,10 +16,11 @@
export { ilertApiRef, ILertClient } from './client';
export type {
AlertEventRequest as EventRequest,
EventRequest as EventRequest,
GetAlertsCountOpts,
GetAlertsOpts,
GetServicesOpts,
GetStatusPagesOpts,
ILertApi,
TableState,
} from './types';
+11
View File
@@ -24,6 +24,7 @@ import {
OnCall,
Schedule,
Service,
StatusPage,
UptimeMonitor,
User,
} from '../types';
@@ -53,6 +54,12 @@ export type GetServicesOpts = {
startIndex?: number;
};
/** @public */
export type GetStatusPagesOpts = {
maxResults?: number;
startIndex?: number;
};
/** @public */
export type EventRequest = {
integrationKey: string;
@@ -109,12 +116,16 @@ export interface ILertApi {
fetchServices(opts?: GetServicesOpts): Promise<Service[]>;
createService(eventRequest: ServiceRequest): Promise<boolean>;
fetchStatusPages(opts?: GetStatusPagesOpts): Promise<StatusPage[]>;
getAlertDetailsURL(alert: Alert): string;
getAlertSourceDetailsURL(alertSource: AlertSource | null): string;
getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string;
getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string;
getScheduleDetailsURL(schedule: Schedule): string;
getServiceDetailsURL(service: Service): string;
getStatusPageDetailsURL(statusPage: StatusPage): string;
getStatusPageURL(statusPage: StatusPage): string;
getUserPhoneNumber(user: User | null): string;
getUserInitials(user: User | null): string;
}
@@ -24,6 +24,7 @@ import React from 'react';
import { AlertsPage } from '../AlertsPage';
import { OnCallSchedulesPage } from '../OnCallSchedulesPage';
import { ServicesPage } from '../ServicesPage';
import { StatusPagesPage } from '../StatusPagePage';
import { UptimeMonitorsPage } from '../UptimeMonitorsPage';
/** @public */
@@ -34,6 +35,7 @@ export const ILertPage = () => {
{ label: 'Alerts' },
{ label: 'Uptime Monitors' },
{ label: 'Services' },
{ label: 'Status pages' },
];
const renderTab = () => {
switch (selectedTab) {
@@ -45,6 +47,8 @@ export const ILertPage = () => {
return <UptimeMonitorsPage />;
case 3:
return <ServicesPage />;
case 4:
return <StatusPagesPage />;
default:
return null;
}
@@ -27,7 +27,7 @@ import { serviceStatusLabels } from '../Service/ServiceStatus';
const OperationalChip = withStyles({
root: {
backgroundColor: '#4caf50',
backgroundColor: '#388E3D',
color: 'white',
margin: 0,
},
@@ -35,28 +35,28 @@ const OperationalChip = withStyles({
const UnderMaintenanceChip = withStyles({
root: {
backgroundColor: '#ffb74d',
backgroundColor: '#616161',
color: 'white',
margin: 0,
},
})(Chip);
const DegradedChip = withStyles({
root: {
backgroundColor: '#d32f2f',
backgroundColor: '#FBC02D',
color: 'white',
margin: 0,
},
})(Chip);
const PartialOutageChip = withStyles({
root: {
backgroundColor: '#d4a5bb',
backgroundColor: '#F57C02',
color: 'white',
margin: 0,
},
})(Chip);
const MajorOutageChip = withStyles({
root: {
backgroundColor: '#28c548',
backgroundColor: '#D22F2E',
color: 'white',
margin: 0,
},
@@ -1,97 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Checkbox from '@material-ui/core/Checkbox';
import FormControl from '@material-ui/core/FormControl';
import ListItemText from '@material-ui/core/ListItemText';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types';
import { alertStatusLabels } from '../Alert/AlertStatus';
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
const useStyles = makeStyles({
root: {
display: 'flex',
},
label: {
marginTop: 8,
marginRight: 4,
},
formControl: {
minWidth: 120,
maxWidth: 300,
},
grow: {
flexGrow: 1,
},
});
export const TableTitle = ({
alertStates,
onAlertStatesChange,
}: {
alertStates: AlertStatus[];
onAlertStatesChange: (states: AlertStatus[]) => void;
}) => {
const classes = useStyles();
const handleAlertStatusSelectChange = (event: any) => {
onAlertStatesChange(event.target.value);
};
return (
<div className={classes.root}>
<Typography noWrap className={classes.label}>
Status:
</Typography>
<FormControl
className={classes.formControl}
variant="outlined"
size="small"
>
<Select
id="alerts-status-select"
multiple
value={alertStates}
onChange={handleAlertStatusSelectChange}
renderValue={(selected: any) => selected.join(', ')}
MenuProps={MenuProps}
>
{[PENDING, ACCEPTED, RESOLVED].map(state => (
<MenuItem key={state} value={state}>
<Checkbox
checked={alertStates.indexOf(state as AlertStatus) > -1}
/>
<ListItemText primary={alertStatusLabels[state]} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
};
@@ -0,0 +1,79 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import React from 'react';
import { ilertApiRef } from '../../api';
import { StatusPage } from '../../types';
import { Link } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
export const StatusPageActionsMenu = ({
statusPage,
}: {
statusPage: StatusPage;
}) => {
const ilertApi = useApi(ilertApiRef);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleCloseMenu = () => {
setAnchorEl(null);
};
return (
<>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
size="small"
>
<MoreVertIcon />
</IconButton>
<Menu
id={`statusPage-actions-menu-${statusPage.id}`}
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleCloseMenu}
PaperProps={{
style: { maxHeight: 48 * 5.5 },
}}
>
<MenuItem key="details" onClick={handleCloseMenu}>
<Typography variant="inherit" noWrap>
<Link to={ilertApi.getStatusPageDetailsURL(statusPage)}>
View in iLert
</Link>
</Typography>
</MenuItem>
<MenuItem>
<Typography variant="inherit" noWrap>
<Link to={`https://${ilertApi.getStatusPageURL(statusPage)}`}>
View status page
</Link>
</Typography>
</MenuItem>
</Menu>
</>
);
};
@@ -0,0 +1,50 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import { ilertApiRef } from '../../api';
import { StatusPage } from '../../types';
import { Link } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
const useStyles = makeStyles({
link: {
lineHeight: '22px',
},
});
export const StatusPageLink = ({
statusPage,
}: {
statusPage: StatusPage | null;
}) => {
const ilertApi = useApi(ilertApiRef);
const classes = useStyles();
if (!statusPage) {
return null;
}
return (
<Link
className={classes.link}
to={ilertApi.getStatusPageDetailsURL(statusPage)}
>
#{statusPage.id}
</Link>
);
};
@@ -0,0 +1,61 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { StatusError, StatusOK } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core/styles';
import Tooltip from '@material-ui/core/Tooltip';
import React from 'react';
import {
DEGRADED,
MAJOR_OUTAGE,
OPERATIONAL,
PARTIAL_OUTAGE,
StatusPage,
UNDER_MAINTENANCE,
} from '../../types';
const useStyles = makeStyles({
denseListIcon: {
marginRight: 0,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
});
export const statusPageStatusLabels = {
[OPERATIONAL]: 'Operational',
[UNDER_MAINTENANCE]: 'Under maintenance',
[DEGRADED]: 'Degraded',
[PARTIAL_OUTAGE]: 'Partial outage',
[MAJOR_OUTAGE]: 'Major outage',
} as Record<string, string>;
export const StatusPageStatus = ({
statusPage,
}: {
statusPage: StatusPage;
}) => {
const classes = useStyles();
return (
<Tooltip title={statusPage.status} placement="top">
<div className={classes.denseListIcon}>
{statusPage.status === 'OPERATIONAL' ? <StatusError /> : <StatusOK />}
</div>
</Tooltip>
);
};
@@ -0,0 +1,49 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import { ilertApiRef } from '../../api';
import { StatusPage } from '../../types';
import { Link } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
const useStyles = makeStyles({
link: {
lineHeight: '22px',
},
});
export const StatusPageURL = ({
statusPage,
}: {
statusPage: StatusPage | null;
}) => {
const ilertApi = useApi(ilertApiRef);
const classes = useStyles();
if (!statusPage) {
return null;
}
const url = ilertApi.getStatusPageURL(statusPage);
return (
<Link className={classes.link} to={`https://${url}`}>
{url}
</Link>
);
};
@@ -0,0 +1,51 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { StatusError, StatusOK } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core/styles';
import Tooltip from '@material-ui/core/Tooltip';
import React from 'react';
import { PRIVATE, PUBLIC, StatusPage } from '../../types';
const useStyles = makeStyles({
denseListIcon: {
marginRight: 0,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
});
export const statusPageVisibilityLabels = {
[PUBLIC]: 'Public',
[PRIVATE]: 'Private',
} as Record<string, string>;
export const StatusPageVisibility = ({
statusPage,
}: {
statusPage: StatusPage;
}) => {
const classes = useStyles();
return (
<Tooltip title={statusPage.visibility} placement="top">
<div className={classes.denseListIcon}>
{statusPage.visibility === 'PUBLIC' ? <StatusError /> : <StatusOK />}
</div>
</Tooltip>
);
};
@@ -13,20 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
export const useNewService = () => {
const [name, setName] = React.useState('');
const [isLoading, setIsLoading] = React.useState(false);
return [
{
name,
isLoading,
},
{
setName,
setIsLoading,
},
] as const;
};
export * from './StatusPageActionsMenu';
export * from './StatusPageStatus';
@@ -0,0 +1,82 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Chip, withStyles } from '@material-ui/core';
import React from 'react';
import {
DEGRADED,
MAJOR_OUTAGE,
OPERATIONAL,
PARTIAL_OUTAGE,
StatusPage,
UNDER_MAINTENANCE,
} from '../../types';
import { statusPageStatusLabels } from '../StatusPage/StatusPageStatus';
const OperationalChip = withStyles({
root: {
backgroundColor: '#388E3D',
color: 'white',
margin: 0,
},
})(Chip);
const UnderMaintenanceChip = withStyles({
root: {
backgroundColor: '#616161',
color: 'white',
margin: 0,
},
})(Chip);
const DegradedChip = withStyles({
root: {
backgroundColor: '#FBC02D',
color: 'white',
margin: 0,
},
})(Chip);
const PartialOutageChip = withStyles({
root: {
backgroundColor: '#F57C02',
color: 'white',
margin: 0,
},
})(Chip);
const MajorOutageChip = withStyles({
root: {
backgroundColor: '#D22F2E',
color: 'white',
margin: 0,
},
})(Chip);
export const StatusChip = ({ statusPage }: { statusPage: StatusPage }) => {
const label = `${statusPageStatusLabels[statusPage.status]}`;
switch (statusPage.status) {
case OPERATIONAL:
return <OperationalChip label={label} size="small" />;
case UNDER_MAINTENANCE:
return <UnderMaintenanceChip label={label} size="small" />;
case DEGRADED:
return <DegradedChip label={label} size="small" />;
case PARTIAL_OUTAGE:
return <PartialOutageChip label={label} size="small" />;
case MAJOR_OUTAGE:
return <MajorOutageChip label={label} size="small" />;
default:
return <Chip label={label} size="small" />;
}
};
@@ -0,0 +1,67 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Content,
ContentHeader,
ResponseErrorPanel,
SupportButton,
} from '@backstage/core-components';
import { AuthenticationError } from '@backstage/errors';
import React from 'react';
import { useStatusPages } from '../../hooks/useStatusPages';
import { MissingAuthorizationHeaderError } from '../Errors';
import { StatusPagesTable } from './StatusPagesTable';
export const StatusPagesPage = () => {
const [
{ tableState, statusPages, isLoading, error },
{ onChangePage, onChangeRowsPerPage, setIsLoading },
] = useStatusPages(true);
if (error) {
if (error instanceof AuthenticationError) {
return (
<Content>
<MissingAuthorizationHeaderError />
</Content>
);
}
return (
<Content>
<ResponseErrorPanel error={error} />
</Content>
);
}
return (
<Content>
<ContentHeader title="Status pages">
<SupportButton>
This helps you to bring iLert into your developer portal.
</SupportButton>
</ContentHeader>
<StatusPagesTable
statusPages={statusPages}
tableState={tableState}
onChangePage={onChangePage}
onChangeRowsPerPage={onChangeRowsPerPage}
isLoading={isLoading}
setIsLoading={setIsLoading}
/>
</Content>
);
};
@@ -0,0 +1,184 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { TableState } from '../../api';
import { StatusPage } from '../../types';
import { VisibilityChip } from './VisibilityChip';
import { Table, TableColumn } from '@backstage/core-components';
import { StatusPageActionsMenu } from '../StatusPage/StatusPageActionsMenu';
import { StatusPageLink } from '../StatusPage/StatusPageLink';
import { StatusPageURL } from '../StatusPage/StatusPageURL';
import { StatusChip } from './StatusChip';
const useStyles = makeStyles(theme => ({
empty: {
padding: theme.spacing(2),
display: 'flex',
justifyContent: 'center',
},
}));
export const StatusPagesTable = ({
statusPages,
tableState,
isLoading,
onChangePage,
onChangeRowsPerPage,
compact,
}: {
statusPages: StatusPage[];
tableState: TableState;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
onChangePage: (page: number) => void;
onChangeRowsPerPage: (pageSize: number) => void;
compact?: boolean;
}) => {
const classes = useStyles();
// const xsColumnStyle = {
// width: '5%',
// maxWidth: '5%',
// };
const smColumnStyle = {
width: '10%',
maxWidth: '10%',
};
// const mdColumnStyle = {
// width: '15%',
// maxWidth: '15%',
// };
// const lgColumnStyle = {
// width: '20%',
// maxWidth: '20%',
// };
const xlColumnStyle = {
width: '30%',
maxWidth: '30%',
};
const idColumn: TableColumn = {
title: 'ID',
field: 'id',
highlight: true,
cellStyle: smColumnStyle,
headerStyle: smColumnStyle,
render: rowData => <StatusPageLink statusPage={rowData as StatusPage} />,
};
const nameColumn: TableColumn = {
title: 'Name',
field: 'name',
cellStyle: !compact ? xlColumnStyle : undefined,
headerStyle: !compact ? xlColumnStyle : undefined,
render: rowData => <Typography>{(rowData as StatusPage).name}</Typography>,
};
const urlColumn: TableColumn = {
title: 'URL',
field: 'url',
cellStyle: smColumnStyle,
headerStyle: smColumnStyle,
render: rowData => <StatusPageURL statusPage={rowData as StatusPage} />,
};
const visibilityColumn: TableColumn = {
title: 'Visibility',
field: 'visibility',
cellStyle: smColumnStyle,
headerStyle: smColumnStyle,
render: rowData => <VisibilityChip statusPage={rowData as StatusPage} />,
};
const statusColumn: TableColumn = {
title: 'Status',
field: 'status',
cellStyle: smColumnStyle,
headerStyle: smColumnStyle,
render: rowData => <StatusChip statusPage={rowData as StatusPage} />,
};
const actionsColumn: TableColumn = {
title: '',
field: '',
cellStyle: smColumnStyle,
headerStyle: smColumnStyle,
render: rowData => (
<StatusPageActionsMenu statusPage={rowData as StatusPage} />
),
};
const columns: TableColumn[] = compact
? [nameColumn, statusColumn, urlColumn, actionsColumn]
: [
idColumn,
nameColumn,
statusColumn,
urlColumn,
visibilityColumn,
actionsColumn,
];
let tableStyle: React.CSSProperties = {};
if (compact) {
tableStyle = {
width: '100%',
maxWidth: '100%',
minWidth: '0',
height: 'calc(100% - 10px)',
boxShadow: 'none !important',
borderRadius: 'none !important',
};
} else {
tableStyle = {
width: '100%',
maxWidth: '100%',
};
}
return (
<Table
style={tableStyle}
options={{
sorting: false,
search: !compact,
paging: !compact,
actionsColumnIndex: -1,
pageSize: tableState.pageSize,
pageSizeOptions: !compact ? [10, 20, 50, 100] : [3, 10, 20, 50, 100],
padding: 'dense',
loadingType: 'overlay',
showEmptyDataSourceMessage: !isLoading,
showTitle: true,
toolbar: true,
}}
emptyContent={
<Typography color="textSecondary" className={classes.empty}>
No status pages
</Typography>
}
title={
<Typography variant="button" color="textSecondary">
STATUS PAGES
</Typography>
}
page={tableState.page}
onPageChange={onChangePage}
onRowsPerPageChange={onChangeRowsPerPage}
// localization={{ header: { actions: undefined } }}
columns={columns}
data={statusPages}
isLoading={isLoading}
/>
);
};
@@ -0,0 +1,48 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Chip, withStyles } from '@material-ui/core';
import React from 'react';
import { PRIVATE, PUBLIC, StatusPage } from '../../types';
import { statusPageVisibilityLabels } from '../StatusPage/StatusPageVisibility';
const PrivateChip = withStyles({
root: {
backgroundColor: '#4caf50',
color: 'white',
margin: 0,
},
})(Chip);
const PublicChip = withStyles({
root: {
backgroundColor: '#ffb74d',
color: 'white',
margin: 0,
},
})(Chip);
export const VisibilityChip = ({ statusPage }: { statusPage: StatusPage }) => {
const label = `${statusPageVisibilityLabels[statusPage.visibility]}`;
switch (statusPage.visibility) {
case PRIVATE:
return <PrivateChip label={label} size="small" />;
case PUBLIC:
return <PublicChip label={label} size="small" />;
default:
return <Chip label={label} size="small" />;
}
};
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './StatusPagesPage';
export * from './StatusPagesTable';
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { AuthenticationError } from '@backstage/errors';
import React from 'react';
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
import { GetStatusPagesOpts, ilertApiRef, TableState } from '../api';
import { StatusPage } from '../types';
export const useStatusPages = (paging: boolean) => {
const ilertApi = useApi(ilertApiRef);
const errorApi = useApi(errorApiRef);
const [tableState, setTableState] = React.useState<TableState>({
page: 0,
pageSize: 10,
});
const [statusPagesList, setStatusPagesList] = React.useState<StatusPage[]>(
[],
);
const [isLoading, setIsLoading] = React.useState(false);
const fetchStatusPagesCall = async () => {
try {
setIsLoading(true);
const opts: GetStatusPagesOpts = {};
if (paging) {
opts.maxResults = tableState.pageSize;
opts.startIndex = tableState.page * tableState.pageSize;
}
const data = await ilertApi.fetchStatusPages(opts);
setStatusPagesList(data || []);
setIsLoading(false);
} catch (e) {
if (!(e instanceof AuthenticationError)) {
errorApi.post(e);
}
setIsLoading(false);
throw e;
}
};
const fetchStatusPages = useAsyncRetry(fetchStatusPagesCall, [tableState]);
const refetchStatusPages = () => {
setTableState({ ...tableState, page: 0 });
Promise.all([fetchStatusPagesCall()]);
};
const error = fetchStatusPages.error;
const retry = () => {
fetchStatusPages.retry();
};
const onChangePage = (page: number) => {
setTableState({ ...tableState, page });
};
const onChangeRowsPerPage = (p: number) => {
setTableState({ ...tableState, pageSize: p });
};
return [
{
tableState,
statusPages: statusPagesList,
isLoading,
error,
},
{
setTableState,
setStatusPagesList,
setIsLoading,
retry,
refetchStatusPages,
onChangePage,
onChangeRowsPerPage,
},
] as const;
};
+18
View File
@@ -251,6 +251,14 @@ export type ServiceStatus =
| typeof PARTIAL_OUTAGE
| typeof MAJOR_OUTAGE;
/** @public */
export const PRIVATE = 'PRIVATE';
/** @public */
export const PUBLIC = 'PUBLIC';
/** @public */
export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC;
/** @public */
export interface AlertSourceEmailPredicate {
field: 'EMAIL_FROM' | 'EMAIL_SUBJECT' | 'EMAIL_BODY';
@@ -422,3 +430,13 @@ export interface Uptime {
export interface UptimePercentage {
p90: number;
}
/** @public */
export interface StatusPage {
id: number;
name: string;
domain: string;
subdomain: string;
visibility: StatusPageVisibility;
status: ServiceStatus;
}