diff --git a/app-config.yaml b/app-config.yaml
index e0e962cb50..e2b03786c4 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -124,7 +124,7 @@ proxy:
St2-Api-Key: ${ST2_API_KEY}
'/puppetdb':
- #target: https://your.puppetdb.instance.com
+ target: https://your.puppetdb.instance.com
organization:
name: My Company
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 362b49cadb..ae1b9c4759 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -116,7 +116,10 @@ import {
EntityRollbarContent,
isRollbarAvailable,
} from '@backstage/plugin-rollbar';
-import { PuppetDbTab, isPuppetDbAvailable } from '@backstage/plugin-puppetdb';
+import {
+ PuppetDbContent,
+ isPuppetDbAvailable,
+} from '@backstage/plugin-puppetdb';
import { EntitySentryContent } from '@backstage/plugin-sentry';
import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
import { EntityTechInsightsScorecardCard } from '@backstage/plugin-tech-insights';
@@ -858,7 +861,7 @@ const resourcePage = (
title="Puppet"
if={isPuppetDbAvailable}
>
-
+
diff --git a/plugins/puppetdb/assets/Events.png b/plugins/puppetdb/assets/Events.png
new file mode 100644
index 0000000000..163ad67813
Binary files /dev/null and b/plugins/puppetdb/assets/Events.png differ
diff --git a/plugins/puppetdb/assets/Logs.png b/plugins/puppetdb/assets/Logs.png
new file mode 100644
index 0000000000..f4e7686764
Binary files /dev/null and b/plugins/puppetdb/assets/Logs.png differ
diff --git a/plugins/puppetdb/assets/Reports.png b/plugins/puppetdb/assets/Reports.png
new file mode 100644
index 0000000000..2bba083fa6
Binary files /dev/null and b/plugins/puppetdb/assets/Reports.png differ
diff --git a/plugins/puppetdb/package.json b/plugins/puppetdb/package.json
index d912bb789b..7793106ed0 100644
--- a/plugins/puppetdb/package.json
+++ b/plugins/puppetdb/package.json
@@ -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",
diff --git a/plugins/puppetdb/src/api/PuppetDbClient.ts b/plugins/puppetdb/src/api/PuppetDbClient.ts
index aecca48118..f97270b5ca 100644
--- a/plugins/puppetdb/src/api/PuppetDbClient.ts
+++ b/plugins/puppetdb/src/api/PuppetDbClient.ts
@@ -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 {
- if (!puppetDbCertName) {
- throw new Error('PuppetDB certname is required');
- }
-
- return this.callApi(
- `/pdb/query/v4/nodes/${encodeURIComponent(puppetDbCertName)}`,
- {},
- );
- }
-
async getPuppetDbReport(
puppetDbReportHash: string,
): Promise {
@@ -82,6 +74,44 @@ export class PuppetDbClient implements PuppetDbApi {
return reports[0];
}
+ async getPuppetDbReportEvents(
+ puppetDbReportHash: string,
+ ): Promise {
+ 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 {
+ 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 {
@@ -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,
});
}
}
diff --git a/plugins/puppetdb/src/api/index.ts b/plugins/puppetdb/src/api/index.ts
index ae442f970d..46d34a8811 100644
--- a/plugins/puppetdb/src/api/index.ts
+++ b/plugins/puppetdb/src/api/index.ts
@@ -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';
diff --git a/plugins/puppetdb/src/api/types.ts b/plugins/puppetdb/src/api/types.ts
index d0ce697481..7fb132e813 100644
--- a/plugins/puppetdb/src/api/types.ts
+++ b/plugins/puppetdb/src/api/types.ts
@@ -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({
* 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;
/**
* Get a list of PuppetDB reports for the specified node.
*
@@ -229,6 +175,7 @@ export type PuppetDbApi = {
getPuppetDbNodeReports(
puppetDbCertName: string,
): Promise;
+
/**
* Get a specific PuppetDB report.
*
@@ -238,4 +185,24 @@ export type PuppetDbApi = {
getPuppetDbReport(
puppetDbReportHash: string,
): Promise;
+
+ /**
+ * Get a specific PuppetDB report events.
+ *
+ * @param puppetDbReportHash - The ID of the report.
+ * @returns Events from a specific PuppetDB report.
+ */
+ getPuppetDbReportEvents(
+ puppetDbReportHash: string,
+ ): Promise;
+
+ /**
+ * Get a specific PuppetDB report logs.
+ *
+ * @param puppetDbReportHash - The ID of the report.
+ * @returns Logs from a specific PuppetDB report.
+ */
+ getPuppetDbReportLogs(
+ puppetDbReportHash: string,
+ ): Promise;
};
diff --git a/plugins/puppetdb/src/components/Node/NodeCard/NodeCard.tsx b/plugins/puppetdb/src/components/Node/NodeCard/NodeCard.tsx
deleted file mode 100644
index 7c3a63bf4b..0000000000
--- a/plugins/puppetdb/src/components/Node/NodeCard/NodeCard.tsx
+++ /dev/null
@@ -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 ;
- } else if (error) {
- return ;
- }
-
- return (
-
-
{JSON.stringify(value, null, 2)}
-
- );
-};
diff --git a/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsEventsTable.tsx b/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsEventsTable.tsx
new file mode 100644
index 0000000000..57bcf1caf3
--- /dev/null
+++ b/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsEventsTable.tsx
@@ -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 ;
+ }
+
+ const columns: TableColumn[] = [
+ {
+ title: 'Run Start Time',
+ field: 'run_start_time',
+ align: 'center',
+ width: '300px',
+ render: rowData => (
+
+ {new Date(Date.parse(rowData.run_start_time)).toLocaleString()}
+
+ ),
+ },
+ {
+ title: 'Run End Time',
+ field: 'run_end_time',
+ align: 'center',
+ width: '300px',
+ render: rowData => (
+
+ {new Date(Date.parse(rowData.run_end_time)).toLocaleString()}
+
+ ),
+ },
+ {
+ title: 'Containing Class',
+ field: 'containing_class',
+ render: rowData => (
+
+ {rowData.containing_class}
+
+ ),
+ },
+ {
+ title: 'Resource',
+ field: 'resource_title',
+ render: rowData => (
+
+ {rowData.resource_type}[{rowData.resource_title}]
+
+ ),
+ },
+ {
+ title: 'Property',
+ field: 'property',
+ render: rowData => {rowData.property},
+ },
+ {
+ title: 'Old Value',
+ field: 'old_value',
+ render: rowData => {rowData.old_value},
+ },
+ {
+ title: 'New Value',
+ field: 'new_value',
+ render: rowData => {rowData.new_value},
+ },
+ {
+ title: 'Status',
+ field: 'status',
+ render: rowData => ,
+ },
+ ];
+
+ return (
+
+ No events
+
+ }
+ title="Latest events"
+ columns={columns}
+ data={value || []}
+ isLoading={loading}
+ />
+ );
+};
diff --git a/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsLogsTable.tsx b/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsLogsTable.tsx
new file mode 100644
index 0000000000..587cf4fffe
--- /dev/null
+++ b/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsLogsTable.tsx
@@ -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(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 ;
+ }
+
+ const columns: TableColumn[] = [
+ {
+ title: 'Level',
+ field: 'level',
+ align: 'center',
+ width: '100px',
+ render: rowData => (
+
+ {rowData.level.toLocaleUpperCase('en-US')}
+
+ ),
+ },
+ {
+ title: 'Timestamp',
+ field: 'time',
+ align: 'center',
+ width: '300px',
+ render: rowData => (
+
+ {new Date(Date.parse(rowData.time)).toLocaleString()}
+
+ ),
+ },
+ {
+ title: 'Source',
+ field: 'source',
+ render: rowData => {rowData.source},
+ },
+ {
+ title: 'Message',
+ field: 'message',
+ render: rowData => {rowData.message},
+ },
+ ];
+
+ return (
+
+ No logs
+
+ }
+ title="Latest logs"
+ columns={columns}
+ data={value || []}
+ isLoading={loading}
+ />
+ );
+};
diff --git a/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsPage.tsx b/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsPage.tsx
new file mode 100644
index 0000000000..db6536fce4
--- /dev/null
+++ b/plugins/puppetdb/src/components/ReportDetailsPage/ReportDetailsPage.tsx
@@ -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(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 (
+