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 ( +
+ + + PuppetDB Reports + + {hash} + + + + setTabIndex(index)} + value={safeTabIndex} + > + {tabs.map((tab, index) => ( + + ))} + + + {safeTabIndex === 0 && } + {safeTabIndex === 1 && } + + + +
+ ); +}; diff --git a/plugins/puppetdb/src/components/ReportDetailsPage/index.ts b/plugins/puppetdb/src/components/ReportDetailsPage/index.ts new file mode 100644 index 0000000000..8c0153c853 --- /dev/null +++ b/plugins/puppetdb/src/components/ReportDetailsPage/index.ts @@ -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'; diff --git a/plugins/puppetdb/src/components/PuppetDbTab/PuppetDbTab.tsx b/plugins/puppetdb/src/components/ReportsPage/ReportsPage.tsx similarity index 58% rename from plugins/puppetdb/src/components/PuppetDbTab/PuppetDbTab.tsx rename to plugins/puppetdb/src/components/ReportsPage/ReportsPage.tsx index 78a830db85..8c99e590b5 100644 --- a/plugins/puppetdb/src/components/PuppetDbTab/PuppetDbTab.tsx +++ b/plugins/puppetdb/src/components/ReportsPage/ReportsPage.tsx @@ -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 ( - - ); - } - - // @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 ( diff --git a/plugins/puppetdb/src/components/Reports/ReportsTable/ReportsTable.tsx b/plugins/puppetdb/src/components/ReportsPage/ReportsTable.tsx similarity index 53% rename from plugins/puppetdb/src/components/Reports/ReportsTable/ReportsTable.tsx rename to plugins/puppetdb/src/components/ReportsPage/ReportsTable.tsx index b927d3ff59..3aeecc70b0 100644 --- a/plugins/puppetdb/src/components/Reports/ReportsTable/ReportsTable.tsx +++ b/plugins/puppetdb/src/components/ReportsPage/ReportsTable.tsx @@ -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 => ( - - {rowData.configuration_version} + + {rowData.configuration_version !== '' ? ( + {rowData.configuration_version} + ) : ( + + (N/A) + + )} ), }, @@ -73,8 +83,9 @@ export const ReportsTable = (props: ReportsTableProps) => { title: 'Start Time', field: 'start_time', align: 'center', + width: '300px', render: rowData => ( - + {new Date(Date.parse(rowData.start_time)).toLocaleString()} ), @@ -83,8 +94,9 @@ export const ReportsTable = (props: ReportsTableProps) => { title: 'End Time', field: 'end_time', align: 'center', + width: '300px', render: rowData => ( - + {new Date(Date.parse(rowData.end_time)).toLocaleString()} ), @@ -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 ( - <> - - {rowData.status.charAt(0).toLocaleUpperCase() + - rowData.status.slice(1)} - - ); - } - - switch (rowData.status) { - case 'failed': - return ( - <> - - {rowData.status.charAt(0).toLocaleUpperCase() + - rowData.status.slice(1)} - - ); - case 'changed': - return ( - <> - - {rowData.status.charAt(0).toLocaleUpperCase() + - rowData.status.slice(1)} - - ); - case 'unchanged': - return ( - <> - - {rowData.status.charAt(0).toLocaleUpperCase() + - rowData.status.slice(1)} - - ); - default: - return ( - <> - - {rowData.status.charAt(0).toLocaleUpperCase() + - rowData.status.slice(1)} - - ); - } - }, + render: rowData => , }, ]; @@ -198,51 +166,4 @@ export const ReportsTable = (props: ReportsTableProps) => { isLoading={loading} /> ); - - /* - return ( - - - - -
- - - Start Time - End Time - Duration - Environment - Configuration Version - Puppet Version - NoOp - Status - - - - {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 ( - {start_date.toLocaleString()} - {end_date.toLocaleString()} - - {duration.getUTCHours().toString().padStart(2, '0')}: - {duration.getUTCMinutes().toString().padStart(2, '0')}: - {duration.getUTCSeconds().toString().padStart(2, '0')}. - {duration.getUTCMilliseconds().toString().padStart(4, '0')} - - {report.environment} - {report.configuration_version} - {report.puppet_version} - {report.noop ? 'Yes' : 'No'} - {report.status} - - )} - )} - -
- - );*/ }; diff --git a/plugins/puppetdb/src/components/Node/index.ts b/plugins/puppetdb/src/components/ReportsPage/index.ts similarity index 81% rename from plugins/puppetdb/src/components/Node/index.ts rename to plugins/puppetdb/src/components/ReportsPage/index.ts index 11d8de14fe..60840e5eb5 100644 --- a/plugins/puppetdb/src/components/Node/index.ts +++ b/plugins/puppetdb/src/components/ReportsPage/index.ts @@ -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'; diff --git a/plugins/puppetdb/src/components/Router.tsx b/plugins/puppetdb/src/components/Router.tsx new file mode 100644 index 0000000000..7a71eb26ed --- /dev/null +++ b/plugins/puppetdb/src/components/Router.tsx @@ -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 ( + + ); + } + + return ( + + } /> + } + /> + + ); +}; diff --git a/plugins/puppetdb/src/components/StatusField/StatusField.tsx b/plugins/puppetdb/src/components/StatusField/StatusField.tsx new file mode 100644 index 0000000000..7c1d1b1efc --- /dev/null +++ b/plugins/puppetdb/src/components/StatusField/StatusField.tsx @@ -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 ( + <> + + {statusUC} + + ); + case 'changed': + return ( + <> + + {statusUC} + + ); + case 'unchanged': + return ( + <> + + {statusUC} + + ); + default: + return ( + <> + + {statusUC} + + ); + } +}; diff --git a/plugins/puppetdb/src/components/PuppetDbTab/index.ts b/plugins/puppetdb/src/components/StatusField/index.ts similarity index 86% rename from plugins/puppetdb/src/components/PuppetDbTab/index.ts rename to plugins/puppetdb/src/components/StatusField/index.ts index 4b96137c54..4f1a8aad1c 100644 --- a/plugins/puppetdb/src/components/PuppetDbTab/index.ts +++ b/plugins/puppetdb/src/components/StatusField/index.ts @@ -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'; diff --git a/plugins/puppetdb/src/index.ts b/plugins/puppetdb/src/index.ts index 091ae1a467..9e3eedd2f3 100644 --- a/plugins/puppetdb/src/index.ts +++ b/plugins/puppetdb/src/index.ts @@ -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'; diff --git a/plugins/puppetdb/src/plugin.ts b/plugins/puppetdb/src/plugin.ts index a819bf74ba..3e44392f77 100644 --- a/plugins/puppetdb/src/plugin.ts +++ b/plugins/puppetdb/src/plugin.ts @@ -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, }), ); diff --git a/plugins/puppetdb/src/routes.ts b/plugins/puppetdb/src/routes.ts index 171c65f728..d6812f9f2c 100644 --- a/plugins/puppetdb/src/routes.ts +++ b/plugins/puppetdb/src/routes.ts @@ -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, }); diff --git a/yarn.lock b/yarn.lock index e53aa294c0..02af474822 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7928,12 +7928,14 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-puppetdb@workspace:plugins/puppetdb" dependencies: + "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@material-ui/core": ^4.12.2