@@ -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 || '';
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
+2
-17
@@ -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';
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user