Implement the functionallity

Signed-off-by: Tomas Dabasinskas <tomas@dabasinskas.net>
This commit is contained in:
Tomas Dabasinskas
2023-04-05 08:15:48 +03:00
parent 13f59da126
commit b3dd0b9aef
24 changed files with 768 additions and 374 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

+2
View File
@@ -34,9 +34,11 @@
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/catalog-model": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/theme": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
+45 -15
View File
@@ -13,7 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PuppetDbApi, PuppetDbNode, PuppetDbReport } from './types';
import {
PuppetDbApi,
PuppetDbReport,
PuppetDbReportEvent,
PuppetDbReportLog,
} from './types';
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
import { ResponseError } from '@backstage/errors';
@@ -51,19 +56,6 @@ export class PuppetDbClient implements PuppetDbApi {
throw await ResponseError.fromResponse(response);
}
async getPuppetDbNode(
puppetDbCertName: string,
): Promise<PuppetDbNode | undefined> {
if (!puppetDbCertName) {
throw new Error('PuppetDB certname is required');
}
return this.callApi(
`/pdb/query/v4/nodes/${encodeURIComponent(puppetDbCertName)}`,
{},
);
}
async getPuppetDbReport(
puppetDbReportHash: string,
): Promise<PuppetDbReport | undefined> {
@@ -82,6 +74,44 @@ export class PuppetDbClient implements PuppetDbApi {
return reports[0];
}
async getPuppetDbReportEvents(
puppetDbReportHash: string,
): Promise<PuppetDbReportEvent[] | undefined> {
if (!puppetDbReportHash) {
throw new Error('PuppetDB report hash is required');
}
const events = (await this.callApi(
`/pdb/query/v4/reports/${puppetDbReportHash}/events`,
{},
)) as PuppetDbReportEvent[];
if (!events || events.length === 0) {
return undefined;
}
return events;
}
async getPuppetDbReportLogs(
puppetDbReportHash: string,
): Promise<PuppetDbReportLog[] | undefined> {
if (!puppetDbReportHash) {
throw new Error('PuppetDB report hash is required');
}
const events = (await this.callApi(
`/pdb/query/v4/reports/${puppetDbReportHash}/logs`,
{},
)) as PuppetDbReportLog[];
if (!events || events.length === 0) {
return undefined;
}
return events;
}
async getPuppetDbNodeReports(
puppetDbCertName: string,
): Promise<PuppetDbReport[] | undefined> {
@@ -92,7 +122,7 @@ export class PuppetDbClient implements PuppetDbApi {
return this.callApi(`/pdb/query/v4/reports`, {
query: `["=","certname","${puppetDbCertName}"]`,
order_by: `[{"field": "start_time", "order": "desc"},{"field": "end_time", "order": "desc"}]`,
limit: 30,
limit: 100,
});
}
}
+1 -1
View File
@@ -13,6 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type { PuppetDbNode, PuppetDbReport } from './types';
export type { PuppetDbReport, PuppetDbReportEvent } from './types';
export { puppetDbApiRef } from './types';
export { PuppetDbClient } from './PuppetDbClient';
+115 -148
View File
@@ -15,179 +15,131 @@
*/
import { createApiRef } from '@backstage/core-plugin-api';
/**
* A node in PuppetDB.
*/
export type PuppetDbNode = {
/**
* The name of the node that the report was received from.
*/
certname: string;
/**
* The environment for the last received catalog.
*/
catalog_environment: string;
/**
* The environment for the last received fact set.
*/
facts_environment: string;
/**
* The environment for the last received report.
*/
report_environment: string;
/**
* The last time a catalog was received. Timestamps are always ISO-8601 compatible date/time strings.
*/
catalog_timestamp: string;
/**
* The last time a fact set was received. Timestamps are always ISO-8601 compatible date/time strings.
*/
facts_timestamp: string;
/**
* The last time a report run was complete. Timestamps are always ISO-8601 compatible date/time strings.
*/
report_timestamp: string;
/**
* The status of the latest report. Possible values come from Puppet's report status.
*/
latest_report_status: string;
/**
* Indicates whether the most recent report for the node was a noop run.
*/
latest_report_noop: boolean;
/**
* Indicates whether the most recent report for the node contained noop events.
*/
latest_report_noop_pending: boolean;
/**
* A flag indicating whether the latest report for the node included events that remediated configuration drift.
*/
latest_report_corrective_change: boolean;
/**
* Cached catalog status of the last puppet run for the node. Possible values are explicitly_requested, on_failure, not_used or null.
*/
cached_catalog_status: string;
/**
* A hash of the latest report for the node.
*/
latest_report_hash: string;
/**
* The job id associated with the latest report.
*/
latest_report_job_id?: string;
/**
* Indicates whether the node is currently in the deactivated state.
*/
deactivated?: boolean;
/**
* Indicates whether the node is currently in the expired state.
*/
expired?: boolean;
};
/**
* Report from a PuppetDB.
*/
export type PuppetDbReport = {
/**
* The name of the node that the report was received from.
*/
/** The name of the node that the report was received from. */
certname: string;
/**
* The ID of the report.
*/
/** The ID of the report. */
hash: string;
/**
* The environment assigned to the node that submitted the report.
*/
/** The environment assigned to the node that submitted the report. */
environment: string;
/**
* The status associated to report's node.
*/
/** The status associated to report's node. */
status: string;
/**
* The job id associated with the report.
*/
/** The job id associated with the report. */
job_id?: string;
/**
* A flag indicating whether the report was produced by a noop run.
*/
/** A flag indicating whether the report was produced by a noop run. */
noop: boolean;
/**
* A flag indicating whether the report contains noop events.
*/
/** A flag indicating whether the report contains noop events. */
noop_pending?: boolean;
/**
* The version of Puppet that generated the report.
*/
/** The version of Puppet that generated the report. */
puppet_version: string;
/**
* The version number of the report format that Puppet used to generate the original report data.
*/
/** The version number of the report format that Puppet used to generate the original report data. */
report_format: number;
/**
* An identifier string that Puppet uses to match a specific catalog for a node to a specific Puppet run.
*/
/** An identifier string that Puppet uses to match a specific catalog for a node to a specific Puppet run. */
configuration_version: string;
/**
* The time on the agent at which the Puppet run began.
*/
/** The time on the agent at which the Puppet run began. */
start_time: string;
/**
* The time on the agent at which the Puppet run ended.
*/
/** The time on the agent at which the Puppet run ended. */
end_time: string;
/**
* The time of catalog submission from the Puppet Server to PuppetDB.
*/
/** The time of catalog submission from the Puppet Server to PuppetDB. */
producer_timestamp: string;
/**
* The time at which PuppetDB received the report.
*/
/** The time at which PuppetDB received the report. */
receive_time: string;
/**
* The certname of the Puppet Server that sent the report to PuppetDB.
*/
/** The certname of the Puppet Server that sent the report to PuppetDB. */
producer: string;
/**
* A string used to identify a Puppet run.
*/
/** A string used to identify a Puppet run. */
transaction_uuid: string;
/**
* A string used to tie a catalog to a report to the catalog used from that Puppet run.
*/
/** A string used to tie a catalog to a report to the catalog used from that Puppet run. */
catalog_uuid: string;
/**
* A string used to tie a catalog to the Puppet code which generated the catalog.
*/
/** A string used to tie a catalog to the Puppet code which generated the catalog. */
code_id: string;
/**
* A string used to identify whether the Puppet run used a cached catalogs.
*/
/** A string used to identify whether the Puppet run used a cached catalogs. */
cached_catalog_status: string;
/**
* Either "agent", "plan", or "any" to restrict the results to reports submitted from that source.
*/
/** Either "agent", "plan", or "any" to restrict the results to reports submitted from that source. */
type: string;
/**
* A flag indicating whether any of the report's events remediated configuration drift.
*/
/** A flag indicating whether any of the report's events remediated configuration drift. */
corrective_change: boolean;
/**
* Report metrics.
*/
/** Report metrics. */
metrics: {
/**
* Metrics data.
*/
/** Metrics data. */
data: PuppetDbReportMetric[];
/**
* Link to the metrics endpoint.
*/
/** Link to the metrics endpoint. */
href: string;
};
};
/**
* Resource event generated from Puppet report.
*/
export type PuppetDbReportEvent = {
/** The name of the node on which the event occurred. */
certname: string;
/** The ID of the report that the event occurred in. */
report: string;
/** The status of the event. Legal values are success, failure, noop, and skipped. */
status: string;
/** The timestamp (from the Puppet agent) at which the event occurred. Timestamps are always ISO-8601 compatible date/time strings. */
timestamp: string;
/** The timestamp (from the Puppet agent) at which the Puppet run began. Timestamps are always ISO-8601 compatible date/time strings. */
run_start_time: string;
/** The timestamp (from the Puppet agent) at which the Puppet run finished. Timestamps are always ISO-8601 compatible date/time strings. */
run_end_time: string;
/** The timestamp (from the PuppetDB server) at which the Puppet report was received. Timestamps are always ISO-8601 compatible date/time strings. */
report_receive_time: string;
/** The type of resource that the event occurred on, such as File, Package, etc. */
resource_type: string;
/** The title of the resource on which the event occurred. */
resource_title: string;
/** The property/parameter of the resource on which the event occurred. For example, on a Package resource, this field might have a value of ensure. */
property: string | null;
/** The name of the resource on which the event occurred. */
name: string | null;
/** The new value that Puppet was attempting to set for the specified resource property. Any rich data values will appear as readable strings. */
new_value: string | null;
/** The previous value of the resource property, which Puppet was attempting to change. Any rich data values will appear as readable strings. */
old_value: string | null;
/** A description (supplied by the resource provider) of what happened during the event. */
message: string | null;
/** The manifest file in which the resource definition is located. */
file: string | null;
/** The line (of the containing manifest file) at which the resource definition can be found. */
line: number | null;
/** The Puppet class where this resource is declared. */
containing_class: string | null;
/** Whether the event occurred in the most recent Puppet run (per-node). */
latest_report?: boolean;
/** The environment associated with the reporting node. */
environment: string;
/** An identifier string that Puppet uses to match a specific catalog for a node to a specific Puppet run. */
configuration_version: string;
/** The containment path associated with the event, as an ordered array that ends with the most specific containing element. */
containment_path: string[];
/** Whether the event represents a "corrective change", meaning the event rectified configuration drift. */
corrective_change: boolean;
};
/**
* Resource log generated from Puppet report.
*/
export type PuppetDbReportLog = {
/** The manifest file in which the resource definition is located. */
file: string | null;
/** The line (of the containing manifest file) at which the resource definition can be found. */
line: number | null;
/** The timestamp (from the Puppet agent) at which the event occurred. Timestamps are always ISO-8601 compatible date/time strings. */
time: string;
/** A description (supplied by the resource provider) of what happened during the event. */
message: string | null;
/** The log level. */
level: string;
/** Log source */
source: string;
/** Resource tags */
tags: string[];
};
/**
* A metric from a PuppetDB report.
*/
@@ -214,12 +166,6 @@ export const puppetDbApiRef = createApiRef<PuppetDbApi>({
* The API provided by the PuppetDB plugin.
*/
export type PuppetDbApi = {
/**
*
* @param puppetDbCertName - The name of the PuppetDB node.
* @returns A PuppetDB node.
*/
getPuppetDbNode(puppetDbCertName: string): Promise<PuppetDbNode | undefined>;
/**
* Get a list of PuppetDB reports for the specified node.
*
@@ -229,6 +175,7 @@ export type PuppetDbApi = {
getPuppetDbNodeReports(
puppetDbCertName: string,
): Promise<PuppetDbReport[] | undefined>;
/**
* Get a specific PuppetDB report.
*
@@ -238,4 +185,24 @@ export type PuppetDbApi = {
getPuppetDbReport(
puppetDbReportHash: string,
): Promise<PuppetDbReport | undefined>;
/**
* Get a specific PuppetDB report events.
*
* @param puppetDbReportHash - The ID of the report.
* @returns Events from a specific PuppetDB report.
*/
getPuppetDbReportEvents(
puppetDbReportHash: string,
): Promise<PuppetDbReportEvent[] | undefined>;
/**
* Get a specific PuppetDB report logs.
*
* @param puppetDbReportHash - The ID of the report.
* @returns Logs from a specific PuppetDB report.
*/
getPuppetDbReportLogs(
puppetDbReportHash: string,
): Promise<PuppetDbReportLog[] | undefined>;
};
@@ -1,46 +0,0 @@
/*
* Copyright 2022 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 React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { Progress, ResponseErrorPanel } from '@backstage/core-components';
import { InfoCard } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { puppetDbApiRef } from '../../../api';
type PuppetDbNodeCardProps = {
certName: string;
};
export const NodeCard = (props: PuppetDbNodeCardProps) => {
const { certName } = props;
const puppetDbApi = useApi(puppetDbApiRef);
const { value, loading, error } = useAsync(async () => {
return puppetDbApi.getPuppetDbReport(certName);
}, [puppetDbApi, certName]);
if (loading) {
return <Progress />;
} else if (error) {
return <ResponseErrorPanel error={error} />;
}
return (
<InfoCard title={`Puppet node: ${certName}`}>
<pre>{JSON.stringify(value, null, 2)}</pre>
</InfoCard>
);
};
@@ -0,0 +1,146 @@
/*
* Copyright 2022 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 Typography from '@material-ui/core/Typography';
import React from 'react';
import { puppetDbApiRef } from '../../api';
import {
ResponseErrorPanel,
Table,
TableColumn,
} from '@backstage/core-components';
import { PuppetDbReportEvent } from '../../api/types';
import useAsync from 'react-use/lib/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core/styles';
import { StatusField } from '../StatusField';
type ReportEventsTableProps = {
hash: string;
};
const useStyles = makeStyles(theme => ({
empty: {
padding: theme.spacing(2),
display: 'flex',
justifyContent: 'center',
},
}));
/**
* Component for displaying PuppetDB report events.
*
* @public
*/
export const ReportDetailsEventsTable = (props: ReportEventsTableProps) => {
const { hash } = props;
const puppetDbApi = useApi(puppetDbApiRef);
const classes = useStyles();
const { value, loading, error } = useAsync(async () => {
return puppetDbApi.getPuppetDbReportEvents(hash);
}, [puppetDbApi, hash]);
if (error) {
return <ResponseErrorPanel error={error} />;
}
const columns: TableColumn<PuppetDbReportEvent>[] = [
{
title: 'Run Start Time',
field: 'run_start_time',
align: 'center',
width: '300px',
render: rowData => (
<Typography noWrap>
{new Date(Date.parse(rowData.run_start_time)).toLocaleString()}
</Typography>
),
},
{
title: 'Run End Time',
field: 'run_end_time',
align: 'center',
width: '300px',
render: rowData => (
<Typography noWrap>
{new Date(Date.parse(rowData.run_end_time)).toLocaleString()}
</Typography>
),
},
{
title: 'Containing Class',
field: 'containing_class',
render: rowData => (
<Typography noWrap title={rowData.file || ''}>
{rowData.containing_class}
</Typography>
),
},
{
title: 'Resource',
field: 'resource_title',
render: rowData => (
<Typography noWrap>
{rowData.resource_type}[{rowData.resource_title}]
</Typography>
),
},
{
title: 'Property',
field: 'property',
render: rowData => <Typography noWrap>{rowData.property}</Typography>,
},
{
title: 'Old Value',
field: 'old_value',
render: rowData => <Typography noWrap>{rowData.old_value}</Typography>,
},
{
title: 'New Value',
field: 'new_value',
render: rowData => <Typography noWrap>{rowData.new_value}</Typography>,
},
{
title: 'Status',
field: 'status',
render: rowData => <StatusField status={rowData.status} />,
},
];
return (
<Table
options={{
sorting: true,
actionsColumnIndex: -1,
loadingType: 'linear',
padding: 'dense',
showEmptyDataSourceMessage: !loading,
showTitle: true,
toolbar: true,
pageSize: 10,
pageSizeOptions: [10],
}}
emptyContent={
<Typography color="textSecondary" className={classes.empty}>
No events
</Typography>
}
title="Latest events"
columns={columns}
data={value || []}
isLoading={loading}
/>
);
};
@@ -0,0 +1,134 @@
/*
* Copyright 2022 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 Typography from '@material-ui/core/Typography';
import React from 'react';
import { puppetDbApiRef } from '../../api';
import {
ResponseErrorPanel,
Table,
TableColumn,
} from '@backstage/core-components';
import { PuppetDbReportLog } from '../../api/types';
import useAsync from 'react-use/lib/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core/styles';
import { BackstageTheme } from '@backstage/theme';
type ReportLogsTableProps = {
hash: string;
};
const useStyles = makeStyles<BackstageTheme>(theme => ({
empty: {
padding: theme.spacing(2),
display: 'flex',
justifyContent: 'center',
},
level_error: {
color: theme.palette.error.light,
},
level_warning: {
color: theme.palette.warning.light,
},
level_notice: {
color: theme.palette.info.light,
},
}));
/**
* Component for displaying PuppetDB report logs.
*
* @public
*/
export const ReportDetailsLogsTable = (props: ReportLogsTableProps) => {
const { hash } = props;
const puppetDbApi = useApi(puppetDbApiRef);
const classes = useStyles();
const { value, loading, error } = useAsync(async () => {
return puppetDbApi.getPuppetDbReportLogs(hash);
}, [puppetDbApi, hash]);
if (error) {
return <ResponseErrorPanel error={error} />;
}
const columns: TableColumn<PuppetDbReportLog>[] = [
{
title: 'Level',
field: 'level',
align: 'center',
width: '100px',
render: rowData => (
<Typography
noWrap
className={
(rowData.level === 'warning' && classes.level_warning) ||
(rowData.level === 'error' && classes.level_error) ||
classes.level_notice
}
>
{rowData.level.toLocaleUpperCase('en-US')}
</Typography>
),
},
{
title: 'Timestamp',
field: 'time',
align: 'center',
width: '300px',
render: rowData => (
<Typography noWrap>
{new Date(Date.parse(rowData.time)).toLocaleString()}
</Typography>
),
},
{
title: 'Source',
field: 'source',
render: rowData => <Typography noWrap>{rowData.source}</Typography>,
},
{
title: 'Message',
field: 'message',
render: rowData => <Typography noWrap>{rowData.message}</Typography>,
},
];
return (
<Table
options={{
sorting: true,
actionsColumnIndex: -1,
loadingType: 'linear',
padding: 'dense',
showEmptyDataSourceMessage: !loading,
showTitle: true,
toolbar: true,
pageSize: 10,
pageSizeOptions: [10],
}}
emptyContent={
<Typography color="textSecondary" className={classes.empty}>
No logs
</Typography>
}
title="Latest logs"
columns={columns}
data={value || []}
isLoading={loading}
/>
);
};
@@ -0,0 +1,106 @@
/*
* Copyright 2020 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 { Link as RouterLink, useParams } from 'react-router-dom';
import { Breadcrumbs, Link } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core/styles';
import { useRouteRef } from '@backstage/core-plugin-api';
import { puppetDbRouteRef } from '../../routes';
import React, { useState } from 'react';
import {
Card,
CardContent,
Tab,
Box,
Typography,
Tabs,
} from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import { ReportDetailsEventsTable } from './ReportDetailsEventsTable';
import { ReportDetailsLogsTable } from './ReportDetailsLogsTable';
const useStyles = makeStyles<BackstageTheme>(theme => ({
cards: {
marginTop: theme.spacing(2),
},
tabs: {
borderBottom: `1px solid ${theme.palette.textVerySubtle}`,
backgroundColor: theme.palette.background.default,
padding: theme.spacing(0, 4),
},
default: {
padding: theme.spacing(2),
fontWeight: theme.typography.fontWeightBold,
color: theme.palette.text.secondary,
textTransform: 'uppercase',
},
selected: {
color: theme.palette.text.primary,
},
}));
/**
* Component for displaying the details of a PuppetDB report.
*
* @public
*/
export const ReportDetailsPage = () => {
const { hash = '' } = useParams();
const classes = useStyles();
const [tabIndex, setTabIndex] = useState(0);
const reportsRouteLink = useRouteRef(puppetDbRouteRef);
const tabs = [
{ id: 'events', label: 'Events' },
{ id: 'logs', label: 'Logs' },
];
const safeTabIndex = tabIndex > tabs.length - 1 ? 0 : tabIndex;
return (
<div>
<Breadcrumbs aria-label="breadcrumb">
<Link component={RouterLink} to={reportsRouteLink()}>
PuppetDB Reports
</Link>
<Typography noWrap>{hash}</Typography>
</Breadcrumbs>
<Card
style={{ position: 'relative', overflow: 'visible' }}
className={classes.cards}
>
<CardContent>
<Tabs
indicatorColor="primary"
onChange={(_, index) => setTabIndex(index)}
value={safeTabIndex}
>
{tabs.map((tab, index) => (
<Tab
className={classes.default}
label={tab.label}
key={tab.id}
value={index}
classes={{ selected: classes.selected }}
/>
))}
</Tabs>
<Box ml={2} pt={2} my={1} display="flex" flexDirection="column">
{safeTabIndex === 0 && <ReportDetailsEventsTable hash={hash} />}
{safeTabIndex === 1 && <ReportDetailsLogsTable hash={hash} />}
</Box>
</CardContent>
</Card>
</div>
);
};
@@ -0,0 +1,18 @@
/*
* Copyright 2020 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 { ReportDetailsPage } from './ReportDetailsPage';
export { ReportDetailsEventsTable } from './ReportDetailsEventsTable';
export { ReportDetailsLogsTable } from './ReportDetailsLogsTable';
@@ -1,5 +1,5 @@
/*
* Copyright 2022 The Backstage Authors
* Copyright 2020 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.
@@ -14,29 +14,20 @@
* limitations under the License.
*/
import React from 'react';
import {
Page,
Content,
MissingAnnotationEmptyState,
} from '@backstage/core-components';
import { ReportsTable } from './ReportsTable';
import { Page, Content } from '@backstage/core-components';
import { useEntity } from '@backstage/plugin-catalog-react';
import { isPuppetDbAvailable } from '../../plugin';
import { ANNOTATION_PUPPET_CERTNAME } from '../../constants';
import { ReportsTable } from '../Reports/ReportsTable/ReportsTable';
import { NodeCard } from '../Node';
export const PuppetDbTab = () => {
/**
* Component to display PuppetDB reports page.
*
* @public
*/
export const ReportsPage = () => {
const { entity } = useEntity();
if (!isPuppetDbAvailable(entity)) {
return (
<MissingAnnotationEmptyState annotation={ANNOTATION_PUPPET_CERTNAME} />
);
}
// @ts-ignore
// TODO(tdabasinskas): Remove the static value after the testing.
const certName = entity.metadata.annotations[ANNOTATION_PUPPET_CERTNAME];
const certName =
entity?.metadata?.annotations?.[ANNOTATION_PUPPET_CERTNAME] ?? '';
return (
<Page themeId="tool">
@@ -13,25 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { puppetDbApiRef, PuppetDbReport } from '../../../api';
import { puppetDbApiRef, PuppetDbReport } from '../../api';
import useAsync from 'react-use/lib/useAsync';
import React from 'react';
import {
Link,
ResponseErrorPanel,
Table,
StatusPending,
StatusRunning,
StatusOK,
StatusAborted,
StatusError,
TableColumn,
} from '@backstage/core-components';
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { Link as RouterLink } from 'react-router-dom';
import { buildRouteRef } from '../../../routes';
import { puppetDbReportRouteRef } from '../../routes';
import { StatusField } from '../StatusField';
type ReportsTableProps = {
certName: string;
@@ -45,10 +41,15 @@ const useStyles = makeStyles(theme => ({
},
}));
/**
* Component for displaying a table of PuppetDB reports for a given node.
*
* @public
*/
export const ReportsTable = (props: ReportsTableProps) => {
const { certName } = props;
const puppetDbApi = useApi(puppetDbApiRef);
const routeLink = useRouteRef(buildRouteRef);
const reportsRouteLink = useRouteRef(puppetDbReportRouteRef);
const classes = useStyles();
const { value, loading, error } = useAsync(async () => {
@@ -64,8 +65,17 @@ export const ReportsTable = (props: ReportsTableProps) => {
title: 'Configuration Version',
field: 'configuration_version',
render: rowData => (
<Link component={RouterLink} to={routeLink({ hash: rowData.hash! })}>
<Typography noWrap>{rowData.configuration_version}</Typography>
<Link
component={RouterLink}
to={reportsRouteLink({ hash: rowData.hash! })}
>
{rowData.configuration_version !== '' ? (
<Typography noWrap>{rowData.configuration_version}</Typography>
) : (
<Typography noWrap>
<em>(N/A)</em>
</Typography>
)}
</Link>
),
},
@@ -73,8 +83,9 @@ export const ReportsTable = (props: ReportsTableProps) => {
title: 'Start Time',
field: 'start_time',
align: 'center',
width: '300px',
render: rowData => (
<Typography>
<Typography noWrap>
{new Date(Date.parse(rowData.start_time)).toLocaleString()}
</Typography>
),
@@ -83,8 +94,9 @@ export const ReportsTable = (props: ReportsTableProps) => {
title: 'End Time',
field: 'end_time',
align: 'center',
width: '300px',
render: rowData => (
<Typography>
<Typography noWrap>
{new Date(Date.parse(rowData.end_time)).toLocaleString()}
</Typography>
),
@@ -92,6 +104,7 @@ export const ReportsTable = (props: ReportsTableProps) => {
{
title: 'Run Duration',
align: 'center',
width: '400px',
render: rowData => {
const start_date = new Date(Date.parse(rowData.start_time));
const end_date = new Date(Date.parse(rowData.end_time));
@@ -125,52 +138,7 @@ export const ReportsTable = (props: ReportsTableProps) => {
title: 'Status',
field: 'status',
align: 'center',
render: rowData => {
if (rowData.noop) {
return (
<>
<StatusAborted />
{rowData.status.charAt(0).toLocaleUpperCase() +
rowData.status.slice(1)}
</>
);
}
switch (rowData.status) {
case 'failed':
return (
<>
<StatusError />
{rowData.status.charAt(0).toLocaleUpperCase() +
rowData.status.slice(1)}
</>
);
case 'changed':
return (
<>
<StatusRunning />
{rowData.status.charAt(0).toLocaleUpperCase() +
rowData.status.slice(1)}
</>
);
case 'unchanged':
return (
<>
<StatusPending />
{rowData.status.charAt(0).toLocaleUpperCase() +
rowData.status.slice(1)}
</>
);
default:
return (
<>
<StatusOK />
{rowData.status.charAt(0).toLocaleUpperCase() +
rowData.status.slice(1)}
</>
);
}
},
render: rowData => <StatusField status={rowData.status} />,
},
];
@@ -198,51 +166,4 @@ export const ReportsTable = (props: ReportsTableProps) => {
isLoading={loading}
/>
);
/*
return (
<TableContainer className={classes.table}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Start Time</TableCell>
<TableCell>End Time</TableCell>
<TableCell>Duration</TableCell>
<TableCell>Environment</TableCell>
<TableCell>Configuration Version</TableCell>
<TableCell>Puppet Version</TableCell>
<TableCell>NoOp</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{value?.map((report) => {
const start_date = new Date(Date.parse(report.start_time));
const end_date = new Date(Date.parse(report.end_time));
const duration = new Date(end_date.getTime() - start_date.getTime());
return (<TableRow>
<TableCell>{start_date.toLocaleString()}</TableCell>
<TableCell>{end_date.toLocaleString()}</TableCell>
<TableCell>
{duration.getUTCHours().toString().padStart(2, '0')}:
{duration.getUTCMinutes().toString().padStart(2, '0')}:
{duration.getUTCSeconds().toString().padStart(2, '0')}.
{duration.getUTCMilliseconds().toString().padStart(4, '0')}
</TableCell>
<TableCell>{report.environment}</TableCell>
<TableCell>{report.configuration_version}</TableCell>
<TableCell>{report.puppet_version}</TableCell>
<TableCell>{report.noop ? 'Yes' : 'No'}</TableCell>
<TableCell>{report.status}</TableCell>
</TableRow>
)}
)}
</TableBody>
</Table>
</TableContainer>
);*/
};
@@ -1,5 +1,5 @@
/*
* Copyright 2022 The Backstage Authors
* Copyright 2020 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.
@@ -13,4 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { NodeCard } from './NodeCard/NodeCard';
export { ReportsPage } from './ReportsPage';
export { ReportsTable } from './ReportsTable';
@@ -0,0 +1,55 @@
/*
* Copyright 2020 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 React from 'react';
import { Routes, Route } from 'react-router-dom';
import { puppetDbReportRouteRef } from '../routes';
import { ANNOTATION_PUPPET_CERTNAME } from '../constants';
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
import { ReportsPage } from './ReportsPage/ReportsPage';
import { ReportDetailsPage } from './ReportDetailsPage';
/**
* Checks if the entity has a puppet certname annotation.
* @param entity - The entity to check for the puppet certname annotation.
*
* @public
*/
export const isPuppetDbAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[ANNOTATION_PUPPET_CERTNAME]);
/** @public */
export const Router = () => {
const { entity } = useEntity();
if (!isPuppetDbAvailable(entity)) {
return (
<MissingAnnotationEmptyState annotation={ANNOTATION_PUPPET_CERTNAME} />
);
}
return (
<Routes>
<Route path="/" element={<ReportsPage />} />
<Route
path={`${puppetDbReportRouteRef.path}`}
element={<ReportDetailsPage />}
/>
</Routes>
);
};
@@ -0,0 +1,68 @@
/*
* Copyright 2020 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 React from 'react';
import {
StatusPending,
StatusRunning,
StatusOK,
StatusError,
} from '@backstage/core-components';
type StatusCellProps = {
status: string;
};
/**
* Component to display status field for Puppet reports and events.
*
* @public
*/
export const StatusField = (props: StatusCellProps) => {
const { status } = props;
const statusUC = status.toLocaleUpperCase('en-US');
switch (status) {
case 'failed':
return (
<>
<StatusError />
{statusUC}
</>
);
case 'changed':
return (
<>
<StatusRunning />
{statusUC}
</>
);
case 'unchanged':
return (
<>
<StatusPending />
{statusUC}
</>
);
default:
return (
<>
<StatusOK />
{statusUC}
</>
);
}
};
@@ -1,5 +1,5 @@
/*
* Copyright 2022 The Backstage Authors
* Copyright 2020 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.
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { PuppetDbTab } from './PuppetDbTab';
export { StatusField } from './StatusField';
+11 -1
View File
@@ -13,4 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { puppetdbPlugin, PuppetDbTab, isPuppetDbAvailable } from './plugin';
export {
puppetdbPlugin,
puppetdbPlugin as plugin,
PuppetDbContent,
} from './plugin';
export {
Router,
isPuppetDbAvailable,
isPuppetDbAvailable as isPluginApplicableToEntity,
} from './components/Router';
export * from './routes';
+12 -28
View File
@@ -21,51 +21,35 @@ import {
fetchApiRef,
} from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
import { Entity } from '@backstage/catalog-model';
import { ANNOTATION_PUPPET_CERTNAME } from './constants';
import { puppetDbApiRef, PuppetDbClient } from './api';
import { puppetDbRouteRef } from './routes';
/**
* Create the PuppetDB frontend plugin.
*
* @public
*/
* */
export const puppetdbPlugin = createPlugin({
id: 'puppetdb',
id: 'puppetDb',
apis: [
createApiFactory({
api: puppetDbApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
factory: ({ discoveryApi, fetchApi }) =>
new PuppetDbClient({
discoveryApi,
fetchApi,
}),
new PuppetDbClient({ discoveryApi, fetchApi }),
}),
],
});
/**
* Checks if the entity has a puppet certname annotation.
* @public
* @param entity - The entity to check for the puppet cername annotation.
*/
export const isPuppetDbAvailable = (entity: Entity) =>
// TODO(tdabasinskas): Remove the `|| true` once testing is done.
Boolean(entity.metadata.annotations?.[ANNOTATION_PUPPET_CERTNAME]) || true;
/**
* Creates a routable extension for the PuppetDB plugin tab.
* Creates a routable extension for the PuppetDB plugin content.
*
* @public
*/
export const PuppetDbTab = puppetdbPlugin.provide(
export const PuppetDbContent = puppetdbPlugin.provide(
createRoutableExtension({
name: 'PuppetdbPage',
component: () =>
import('./components/PuppetDbTab').then(m => m.PuppetDbTab),
mountPoint: rootRouteRef,
name: 'PuppetDbContent',
component: () => import('./components/Router').then(m => m.Router),
mountPoint: puppetDbRouteRef,
}),
);
+5 -3
View File
@@ -15,12 +15,14 @@
*/
import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
/** @public */
export const puppetDbRouteRef = createRouteRef({
id: 'puppetdb',
});
export const buildRouteRef = createSubRouteRef({
/** @public */
export const puppetDbReportRouteRef = createSubRouteRef({
id: 'puppetdb/report',
path: '/:hash',
parent: rootRouteRef,
parent: puppetDbRouteRef,
});