Merge pull request #17260 from tdabasinskas/puppetdb-frontend
Add new puppetdb-plugin
This commit is contained in:
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,63 @@
|
||||
# PuppetDB Plugin
|
||||
|
||||
A frontend plugin to integrate PuppetDB with Backstage. When combined with the
|
||||
[catalog-backend-module-puppetdb](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) plugin, this
|
||||
frontend plugin allows viewing PuppetDB reports, including their logs and events, of Backstage resource entities.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To get started, you need a running instance of PuppetDB. You can find instructions on how to install it
|
||||
[here](https://www.puppet.com/docs/puppetdb/7/install_via_module.html).
|
||||
The PuppetDB [should be configured](https://www.puppet.com/docs/puppetdb/7/configure.html#host) to allow being accessed from your Backstage instance.
|
||||
|
||||
In addition, your Backstage instance need to either have
|
||||
[catalog-backend-module-puppetdb](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) plugin installed
|
||||
or you need to ensure your Resource entities have `puppet.com/certname` annotation set to the PuppetDB node name in some other way.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Install the plugin with `yarn` in the root of your Backstage application directory:
|
||||
|
||||
```bash
|
||||
yarn --cwd packages/app add @backstage/plugin-puppetdb
|
||||
```
|
||||
|
||||
1. Import and use the plugin in `packages/app/src/App.tsx`:
|
||||
|
||||
```tsx
|
||||
import { PuppetDbPage } from '@backstage/plugin-puppetdb';
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
{/* ...other routes */}
|
||||
<Route path="/puppetdb" element={<PuppetDbPage />} />
|
||||
</FlatRoutes>
|
||||
);
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
1. Configure `puppetdb` proxy. As this plugin uses the Backstage proxy to securely communicate with PuppetDB API,
|
||||
add the following to your `app-config.yaml` to enable this configuration:
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
'/puppetdb':
|
||||
target: https://your.puppetdb.instance.com
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
#### Main page with the reports list:
|
||||
|
||||

|
||||
|
||||
#### Events for the specific report:
|
||||
|
||||

|
||||
|
||||
#### Logs of the specific report:
|
||||
|
||||

|
||||
@@ -0,0 +1,35 @@
|
||||
## API Report File for "@backstage/plugin-puppetdb"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PathParams } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { SubRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
export const isPuppetDbAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// @public
|
||||
export const PuppetDbPage: () => JSX.Element;
|
||||
|
||||
// @public
|
||||
const puppetdbPlugin: BackstagePlugin<{}, {}, {}>;
|
||||
export { puppetdbPlugin as plugin };
|
||||
export { puppetdbPlugin };
|
||||
|
||||
// @public (undocumented)
|
||||
export const puppetDbReportRouteRef: SubRouteRef<PathParams<'/:hash'>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const puppetDbRouteRef: RouteRef<undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
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 |
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { puppetdbPlugin } from '../src';
|
||||
|
||||
createDevApp().registerPlugin(puppetdbPlugin).render();
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@backstage/plugin-puppetdb",
|
||||
"description": "Backstage plugin to visualize resource information and Puppet facts from PuppetDB.",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/puppetdb"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"puppetdb",
|
||||
"puppet"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"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",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0",
|
||||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/core-app-api": "workspace:^",
|
||||
"@backstage/dev-utils": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^1.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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 { UrlPatternDiscovery } from '@backstage/core-app-api';
|
||||
import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { PuppetDbClient } from './PuppetDbClient';
|
||||
import {
|
||||
PuppetDbReport,
|
||||
PuppetDbReportEvent,
|
||||
PuppetDbReportLog,
|
||||
} from './types';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
const reports: PuppetDbReport[] = [
|
||||
{
|
||||
hash: 'hash1',
|
||||
puppet_version: 'version1',
|
||||
report_format: 1,
|
||||
certname: 'node1',
|
||||
start_time: '2021-01-01T00:00:00Z',
|
||||
end_time: '2021-01-01T00:00:00Z',
|
||||
producer_timestamp: '2021-01-01T00:00:00Z',
|
||||
receive_time: '2021-01-01T00:00:00Z',
|
||||
producer: 'producer1',
|
||||
transaction_uuid: 'uuid1',
|
||||
catalog_uuid: 'uuid1',
|
||||
code_id: 'id1',
|
||||
cached_catalog_status: 'status1',
|
||||
type: 'type1',
|
||||
corrective_change: false,
|
||||
configuration_version: 'version1',
|
||||
environment: 'production',
|
||||
noop: false,
|
||||
status: 'changed',
|
||||
},
|
||||
{
|
||||
hash: 'hash2',
|
||||
puppet_version: 'version2',
|
||||
report_format: 2,
|
||||
certname: 'node1',
|
||||
start_time: '2021-01-01T00:00:00Z',
|
||||
end_time: '2021-01-01T00:00:00Z',
|
||||
producer_timestamp: '2021-01-01T00:00:00Z',
|
||||
receive_time: '2021-01-01T00:00:00Z',
|
||||
producer: 'producer2',
|
||||
transaction_uuid: 'uuid2',
|
||||
catalog_uuid: 'uuid2',
|
||||
code_id: 'id2',
|
||||
cached_catalog_status: 'status2',
|
||||
type: 'type2',
|
||||
corrective_change: false,
|
||||
configuration_version: 'version2',
|
||||
environment: 'production',
|
||||
noop: false,
|
||||
status: 'failed',
|
||||
},
|
||||
];
|
||||
|
||||
const events: PuppetDbReportEvent[] = [
|
||||
{
|
||||
name: 'event1',
|
||||
message: 'message1',
|
||||
file: 'file1',
|
||||
line: 1,
|
||||
certname: 'node1',
|
||||
report: 'hash1',
|
||||
timestamp: '2021-01-01T00:00:00Z',
|
||||
report_receive_time: '2021-01-01T00:00:00Z',
|
||||
run_start_time: '2021-01-01T00:00:00Z',
|
||||
containing_class: 'class1',
|
||||
environment: 'production',
|
||||
configuration_version: 'version1',
|
||||
containment_path: ['path1'],
|
||||
corrective_change: false,
|
||||
run_end_time: '2021-01-01T00:00:00Z',
|
||||
resource_type: 'type1',
|
||||
resource_title: 'title1',
|
||||
property: 'property1',
|
||||
old_value: 'old1',
|
||||
new_value: 'new1',
|
||||
status: 'changed',
|
||||
},
|
||||
{
|
||||
name: 'event2',
|
||||
message: 'message2',
|
||||
file: 'file2',
|
||||
line: 2,
|
||||
certname: 'node1',
|
||||
report: 'hash1',
|
||||
timestamp: '2021-01-01T00:00:00Z',
|
||||
report_receive_time: '2021-01-01T00:00:00Z',
|
||||
run_start_time: '2021-01-01T00:00:00Z',
|
||||
containing_class: 'class2',
|
||||
environment: 'production',
|
||||
configuration_version: 'version2',
|
||||
containment_path: ['path2'],
|
||||
corrective_change: false,
|
||||
run_end_time: '2021-01-01T00:00:00Z',
|
||||
resource_type: 'type2',
|
||||
resource_title: 'title2',
|
||||
property: 'property2',
|
||||
old_value: 'old2',
|
||||
new_value: 'new2',
|
||||
status: 'failed',
|
||||
},
|
||||
];
|
||||
|
||||
const logs: PuppetDbReportLog[] = [
|
||||
{
|
||||
level: 'level1',
|
||||
file: 'file1',
|
||||
line: 1,
|
||||
time: '2021-01-01T00:00:00Z',
|
||||
message: 'message1',
|
||||
source: 'source1',
|
||||
tags: ['tag1'],
|
||||
},
|
||||
{
|
||||
level: 'level2',
|
||||
file: 'file2',
|
||||
line: 2,
|
||||
time: '2021-01-01T00:00:00Z',
|
||||
message: 'message2',
|
||||
source: 'source2',
|
||||
tags: ['tag2'],
|
||||
},
|
||||
];
|
||||
|
||||
describe('PuppetDbClient', () => {
|
||||
setupRequestMockHandlers(server);
|
||||
|
||||
const puppetDbCertName = 'node1';
|
||||
const mockBaseUrl = 'http://backstage:9191/api/proxy';
|
||||
const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl);
|
||||
let client: PuppetDbClient;
|
||||
|
||||
const setupHandlers = () => {
|
||||
server.use(
|
||||
rest.get(
|
||||
`${mockBaseUrl}/puppetdb/pdb/query/v4/reports`,
|
||||
(req, res, ctx) => {
|
||||
if (
|
||||
req.url.searchParams.get('query') !==
|
||||
`["=","certname","${puppetDbCertName}"]`
|
||||
) {
|
||||
return res(ctx.status(400));
|
||||
}
|
||||
|
||||
return res(ctx.status(200), ctx.json(reports));
|
||||
},
|
||||
),
|
||||
|
||||
rest.get(
|
||||
`${mockBaseUrl}/puppetdb/pdb/query/v4/reports/hash1/events`,
|
||||
(_, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(events));
|
||||
},
|
||||
),
|
||||
|
||||
rest.get(
|
||||
`${mockBaseUrl}/puppetdb/pdb/query/v4/reports/hash1/logs`,
|
||||
(_, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(logs));
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setupHandlers();
|
||||
client = new PuppetDbClient({
|
||||
discoveryApi: discoveryApi,
|
||||
fetchApi: new MockFetchApi(),
|
||||
});
|
||||
});
|
||||
|
||||
it('getPuppetDbNodeReports should return reports', async () => {
|
||||
const got = await client.getPuppetDbNodeReports(puppetDbCertName);
|
||||
expect(got).toEqual(reports);
|
||||
});
|
||||
|
||||
it('getPuppetDbReportEvents should return events', async () => {
|
||||
const got = await client.getPuppetDbReportEvents('hash1');
|
||||
expect(got).toEqual(events);
|
||||
});
|
||||
|
||||
it('getPuppetDbReportLogs should return logs', async () => {
|
||||
const got = await client.getPuppetDbReportLogs('hash1');
|
||||
expect(got).toEqual(logs);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 {
|
||||
PuppetDbApi,
|
||||
PuppetDbReport,
|
||||
PuppetDbReportEvent,
|
||||
PuppetDbReportLog,
|
||||
} from './types';
|
||||
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
export class PuppetDbClient implements PuppetDbApi {
|
||||
discoveryApi: DiscoveryApi;
|
||||
fetchApi: FetchApi;
|
||||
|
||||
constructor({
|
||||
discoveryApi,
|
||||
fetchApi,
|
||||
}: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
fetchApi: FetchApi;
|
||||
}) {
|
||||
this.discoveryApi = discoveryApi;
|
||||
this.fetchApi = fetchApi;
|
||||
}
|
||||
|
||||
private async callApi<T>(
|
||||
path: string,
|
||||
query: { [key in string]: any },
|
||||
): Promise<T | undefined> {
|
||||
const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/puppetdb`;
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${apiUrl}${path}?${new URLSearchParams(query).toString()}`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
if (response.ok) {
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
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> {
|
||||
if (!puppetDbCertName) {
|
||||
throw new Error('PuppetDB certname is required');
|
||||
}
|
||||
|
||||
return this.callApi(`/pdb/query/v4/reports`, {
|
||||
query: `["=","certname","${puppetDbCertName}"]`,
|
||||
order_by: `[{"field": "start_time", "order": "desc"},{"field": "end_time", "order": "desc"}]`,
|
||||
limit: 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export type {
|
||||
PuppetDbReport,
|
||||
PuppetDbReportEvent,
|
||||
PuppetDbReportLog,
|
||||
} from './types';
|
||||
export { puppetDbApiRef } from './types';
|
||||
export { PuppetDbClient } from './PuppetDbClient';
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* Report from a PuppetDB.
|
||||
*/
|
||||
export type PuppetDbReport = {
|
||||
/** The name of the node that the report was received from. */
|
||||
certname: string;
|
||||
/** The ID of the report. */
|
||||
hash: string;
|
||||
/** The environment assigned to the node that submitted the report. */
|
||||
environment: string;
|
||||
/** The status associated to report's node. */
|
||||
status: string;
|
||||
/** The job id associated with the report. */
|
||||
job_id?: string;
|
||||
/** A flag indicating whether the report was produced by a noop run. */
|
||||
noop: boolean;
|
||||
/** A flag indicating whether the report contains noop events. */
|
||||
noop_pending?: boolean;
|
||||
/** 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. */
|
||||
report_format: number;
|
||||
/** 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. */
|
||||
start_time: string;
|
||||
/** 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. */
|
||||
producer_timestamp: string;
|
||||
/** The time at which PuppetDB received the report. */
|
||||
receive_time: string;
|
||||
/** The certname of the Puppet Server that sent the report to PuppetDB. */
|
||||
producer: string;
|
||||
/** 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. */
|
||||
catalog_uuid: string;
|
||||
/** 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. */
|
||||
cached_catalog_status: string;
|
||||
/** 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. */
|
||||
corrective_change: boolean;
|
||||
/** Report metrics. */
|
||||
metrics?: {
|
||||
/** Metrics data. */
|
||||
data: PuppetDbReportMetric[];
|
||||
/** 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;
|
||||
/** The name of the resource on which the event occurred. */
|
||||
name?: string;
|
||||
/** 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;
|
||||
/** 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;
|
||||
/** A description (supplied by the resource provider) of what happened during the event. */
|
||||
message?: string;
|
||||
/** The manifest file in which the resource definition is located. */
|
||||
file?: string;
|
||||
/** The line (of the containing manifest file) at which the resource definition can be found. */
|
||||
line?: number;
|
||||
/** The Puppet class where this resource is declared. */
|
||||
containing_class?: string;
|
||||
/** 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;
|
||||
/** The line (of the containing manifest file) at which the resource definition can be found. */
|
||||
line?: number;
|
||||
/** 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;
|
||||
/** The log level. */
|
||||
level: string;
|
||||
/** Log source */
|
||||
source: string;
|
||||
/** Resource tags */
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A metric from a PuppetDB report.
|
||||
*/
|
||||
export type PuppetDbReportMetric = {
|
||||
/**
|
||||
* The name of the metric.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The value of the metric.
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The category of the metric.
|
||||
*/
|
||||
category: string;
|
||||
};
|
||||
|
||||
export const puppetDbApiRef = createApiRef<PuppetDbApi>({
|
||||
id: 'plugin.puppetdb.service',
|
||||
});
|
||||
|
||||
/**
|
||||
* The API provided by the PuppetDB plugin.
|
||||
*/
|
||||
export type PuppetDbApi = {
|
||||
/**
|
||||
* Get a list of PuppetDB reports for the specified node.
|
||||
*
|
||||
* @param puppetDbCertName - The name of the node that the report was received from.
|
||||
* @returns A list of PuppetDB reports for the specified node.
|
||||
*/
|
||||
getPuppetDbNodeReports(
|
||||
puppetDbCertName: 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>;
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 { ReportDetailsEventsTable } from './ReportDetailsEventsTable';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { puppetDbApiRef, PuppetDbReportEvent } from '../../api';
|
||||
import { PuppetDbClient } from '../../api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { puppetDbRouteRef } from '../../routes';
|
||||
|
||||
const events: PuppetDbReportEvent[] = [
|
||||
{
|
||||
name: 'event3',
|
||||
message: 'message1',
|
||||
file: 'file1',
|
||||
line: 1,
|
||||
certname: 'node1',
|
||||
report: 'hash1',
|
||||
timestamp: '2021-01-01T00:00:00Z',
|
||||
report_receive_time: '2021-01-01T00:00:00Z',
|
||||
run_start_time: '2021-01-01T00:00:00Z',
|
||||
containing_class: 'class1',
|
||||
environment: 'production',
|
||||
configuration_version: 'version1',
|
||||
containment_path: ['path1'],
|
||||
corrective_change: false,
|
||||
run_end_time: '2021-01-01T00:00:00Z',
|
||||
resource_type: 'type1',
|
||||
resource_title: 'title1',
|
||||
property: 'property1',
|
||||
old_value: 'old1',
|
||||
new_value: 'new1',
|
||||
status: 'changed',
|
||||
},
|
||||
{
|
||||
name: 'event2',
|
||||
message: 'message2',
|
||||
file: 'file2',
|
||||
line: 2,
|
||||
certname: 'node1',
|
||||
report: 'hash1',
|
||||
timestamp: '2021-01-01T00:00:00Z',
|
||||
report_receive_time: '2021-01-01T00:00:00Z',
|
||||
run_start_time: '2021-01-01T00:00:00Z',
|
||||
containing_class: 'class2',
|
||||
environment: 'production',
|
||||
configuration_version: 'version2',
|
||||
containment_path: ['path2'],
|
||||
corrective_change: false,
|
||||
run_end_time: '2021-01-01T00:00:00Z',
|
||||
resource_type: 'type2',
|
||||
resource_title: 'title2',
|
||||
property: 'property2',
|
||||
old_value: 'old2',
|
||||
new_value: 'new2',
|
||||
status: 'failed',
|
||||
},
|
||||
];
|
||||
|
||||
const mockPuppetDbApi: Partial<PuppetDbClient> = {
|
||||
getPuppetDbReportEvents: async () => events,
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
|
||||
|
||||
const hash = 'hash';
|
||||
|
||||
describe('ReportEventsTable', () => {
|
||||
it('should render', async () => {
|
||||
const render = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ReportDetailsEventsTable hash={hash} />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/puppetdb/report/:hash': puppetDbRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(render.getByText('Latest events')).toBeInTheDocument();
|
||||
expect(render.getByText('Containing Class')).toBeInTheDocument();
|
||||
expect(render.getByText('Resource')).toBeInTheDocument();
|
||||
expect(render.getByText('Property')).toBeInTheDocument();
|
||||
expect(render.getByText('Old Value')).toBeInTheDocument();
|
||||
expect(render.getByText('New Value')).toBeInTheDocument();
|
||||
expect(render.getByText('Status')).toBeInTheDocument();
|
||||
expect(render.getByText('property2')).toBeInTheDocument();
|
||||
expect(render.getByText('old1')).toBeInTheDocument();
|
||||
expect(render.getByText('old2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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, PuppetDbReportEvent } from '../../api';
|
||||
import {
|
||||
ResponseErrorPanel,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
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,74 @@
|
||||
/*
|
||||
* 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 { ReportDetailsLogsTable } from './ReportDetailsLogsTable';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { puppetDbApiRef, PuppetDbReportLog } from '../../api';
|
||||
import { PuppetDbClient } from '../../api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { puppetDbRouteRef } from '../../routes';
|
||||
|
||||
const logs: PuppetDbReportLog[] = [
|
||||
{
|
||||
level: 'level1',
|
||||
file: 'file1',
|
||||
line: 1,
|
||||
time: '2021-01-01T00:00:00Z',
|
||||
message: 'message1',
|
||||
source: 'source1',
|
||||
tags: ['tag1'],
|
||||
},
|
||||
{
|
||||
level: 'level2',
|
||||
file: 'file2',
|
||||
line: 2,
|
||||
time: '2021-01-01T00:00:00Z',
|
||||
message: 'message2',
|
||||
source: 'source2',
|
||||
tags: ['tag2'],
|
||||
},
|
||||
];
|
||||
|
||||
const mockPuppetDbApi: Partial<PuppetDbClient> = {
|
||||
getPuppetDbReportLogs: async () => logs,
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
|
||||
|
||||
const hash = 'hash';
|
||||
|
||||
describe('ReportLogsTable', () => {
|
||||
it('should render', async () => {
|
||||
const render = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ReportDetailsLogsTable hash={hash} />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/puppetdb/report/:hash': puppetDbRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(render.getByText('Latest logs')).toBeInTheDocument();
|
||||
expect(render.getByText('Level')).toBeInTheDocument();
|
||||
expect(render.getByText('Timestamp')).toBeInTheDocument();
|
||||
expect(render.getByText('Source')).toBeInTheDocument();
|
||||
expect(render.getByText('Message')).toBeInTheDocument();
|
||||
expect(render.getByText('source1')).toBeInTheDocument();
|
||||
expect(render.getByText('source2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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, PuppetDbReportLog } from '../../api';
|
||||
import {
|
||||
ResponseErrorPanel,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
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,45 @@
|
||||
/*
|
||||
* 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 { ReportDetailsPage } from './ReportDetailsPage';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { puppetDbRouteRef } from '../../routes';
|
||||
import { puppetDbApiRef, PuppetDbClient } from '../../api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
|
||||
const mockPuppetDbApi: Partial<PuppetDbClient> = {
|
||||
getPuppetDbReportEvents: async () => [],
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
|
||||
|
||||
describe('ReportDetailsPage', () => {
|
||||
it('should render', async () => {
|
||||
const render = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ReportDetailsPage />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/puppetdb/report': puppetDbRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(render.getByText('PuppetDB Reports')).toBeInTheDocument();
|
||||
expect(render.getByText('Events')).toBeInTheDocument();
|
||||
expect(render.getByText('Logs')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 { ReportsPage } from './ReportsPage';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { puppetDbApiRef, PuppetDbClient } from '../../api';
|
||||
import { ANNOTATION_PUPPET_CERTNAME } from '../../constants';
|
||||
import { Entity } from '@backstage/catalog-model/';
|
||||
import { isPuppetDbAvailable } from '../Router';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { puppetDbRouteRef } from '../../routes';
|
||||
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'test',
|
||||
annotations: {
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithoutAnnotations: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'test',
|
||||
annotations: {},
|
||||
},
|
||||
};
|
||||
|
||||
const mockPuppetDbApi: Partial<PuppetDbClient> = {
|
||||
getPuppetDbNodeReports: async () => [],
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
|
||||
|
||||
describe('isPuppetDbAvailable', () => {
|
||||
describe('when entity has no annotations', () => {
|
||||
it('returns false', () => {
|
||||
expect(isPuppetDbAvailable(entityWithoutAnnotations)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has the puppet annotation', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPuppetDbAvailable(entity)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReportsPage', () => {
|
||||
it('should render', async () => {
|
||||
const render = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<ReportsPage />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/puppetdb': puppetDbRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(
|
||||
render.getByText('Latest PuppetDB reports from node node1'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { ReportsTable } from './ReportsTable';
|
||||
import { Page, Content } from '@backstage/core-components';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { ANNOTATION_PUPPET_CERTNAME } from '../../constants';
|
||||
|
||||
/**
|
||||
* Component to display PuppetDB reports page.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const ReportsPage = () => {
|
||||
const { entity } = useEntity();
|
||||
const certName =
|
||||
entity?.metadata?.annotations?.[ANNOTATION_PUPPET_CERTNAME] ?? '';
|
||||
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Content>
|
||||
<ReportsTable certName={certName} />
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 { ReportsTable } from './ReportsTable';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { puppetDbApiRef, PuppetDbReport, PuppetDbClient } from '../../api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { puppetDbRouteRef } from '../../routes';
|
||||
|
||||
const reports: PuppetDbReport[] = [
|
||||
{
|
||||
hash: 'hash1',
|
||||
puppet_version: 'version1',
|
||||
report_format: 1,
|
||||
certname: 'node1',
|
||||
start_time: '2021-01-01T00:00:00Z',
|
||||
end_time: '2021-01-01T00:00:00Z',
|
||||
producer_timestamp: '2021-01-01T00:00:00Z',
|
||||
receive_time: '2021-01-01T00:00:00Z',
|
||||
producer: 'producer1',
|
||||
transaction_uuid: 'uuid1',
|
||||
catalog_uuid: 'uuid1',
|
||||
code_id: 'id1',
|
||||
cached_catalog_status: 'status1',
|
||||
type: 'type1',
|
||||
corrective_change: false,
|
||||
configuration_version: 'version1',
|
||||
environment: 'production',
|
||||
noop: true,
|
||||
status: 'changed',
|
||||
},
|
||||
{
|
||||
hash: 'hash2',
|
||||
puppet_version: 'version2',
|
||||
report_format: 2,
|
||||
certname: 'node1',
|
||||
start_time: '2021-01-01T00:00:00Z',
|
||||
end_time: '2021-01-01T00:00:00Z',
|
||||
producer_timestamp: '2021-01-01T00:00:00Z',
|
||||
receive_time: '2021-01-01T00:00:00Z',
|
||||
producer: 'producer2',
|
||||
transaction_uuid: 'uuid2',
|
||||
catalog_uuid: 'uuid2',
|
||||
code_id: 'id2',
|
||||
cached_catalog_status: 'status2',
|
||||
type: 'type2',
|
||||
corrective_change: false,
|
||||
configuration_version: 'version2',
|
||||
environment: 'production',
|
||||
noop: false,
|
||||
status: 'failed',
|
||||
},
|
||||
];
|
||||
|
||||
const mockPuppetDbApi: Partial<PuppetDbClient> = {
|
||||
getPuppetDbNodeReports: async () => reports,
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
|
||||
|
||||
const certName = 'node1';
|
||||
|
||||
describe('ReportsTable', () => {
|
||||
it('should render', async () => {
|
||||
const render = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ReportsTable certName={certName} />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/puppetdb': puppetDbRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
render.getByText('Latest PuppetDB reports from node node1'),
|
||||
).toBeInTheDocument();
|
||||
expect(render.getByText('Configuration Version')).toBeInTheDocument();
|
||||
expect(render.getByText('Start Time')).toBeInTheDocument();
|
||||
expect(render.getByText('End Time')).toBeInTheDocument();
|
||||
expect(render.getByText('Run Duration')).toBeInTheDocument();
|
||||
expect(render.getByText('Mode')).toBeInTheDocument();
|
||||
expect(render.getByText('Status')).toBeInTheDocument();
|
||||
expect(render.getByText('NO-NOOP')).toBeInTheDocument();
|
||||
expect(render.getByText('NOOP')).toBeInTheDocument();
|
||||
expect(render.getByText('FAILED')).toBeInTheDocument();
|
||||
expect(render.getByText('CHANGED')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 { puppetDbApiRef, PuppetDbReport } from '../../api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import React from 'react';
|
||||
import {
|
||||
Link,
|
||||
ResponseErrorPanel,
|
||||
Table,
|
||||
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 { puppetDbReportRouteRef } from '../../routes';
|
||||
import { StatusField } from '../StatusField';
|
||||
|
||||
type ReportsTableProps = {
|
||||
certName: string;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
empty: {
|
||||
padding: theme.spacing(2),
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* 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 reportsRouteLink = useRouteRef(puppetDbReportRouteRef);
|
||||
const classes = useStyles();
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
return puppetDbApi.getPuppetDbNodeReports(certName);
|
||||
}, [puppetDbApi, certName]);
|
||||
|
||||
if (error) {
|
||||
return <ResponseErrorPanel error={error} />;
|
||||
}
|
||||
|
||||
const columns: TableColumn<PuppetDbReport>[] = [
|
||||
{
|
||||
title: 'Configuration Version',
|
||||
field: 'configuration_version',
|
||||
render: rowData => (
|
||||
<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>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Start Time',
|
||||
field: 'start_time',
|
||||
align: 'center',
|
||||
width: '300px',
|
||||
render: rowData => (
|
||||
<Typography noWrap>
|
||||
{new Date(Date.parse(rowData.start_time)).toLocaleString()}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'End Time',
|
||||
field: 'end_time',
|
||||
align: 'center',
|
||||
width: '300px',
|
||||
render: rowData => (
|
||||
<Typography noWrap>
|
||||
{new Date(Date.parse(rowData.end_time)).toLocaleString()}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
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));
|
||||
const duration = new Date(end_date.getTime() - start_date.getTime());
|
||||
return (
|
||||
<Typography noWrap>
|
||||
{duration.getUTCHours().toString().padStart(2, '0')}:
|
||||
{duration.getUTCMinutes().toString().padStart(2, '0')}:
|
||||
{duration.getUTCSeconds().toString().padStart(2, '0')}.
|
||||
{duration.getUTCMilliseconds().toString().padStart(4, '0')}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Environment',
|
||||
field: 'environment',
|
||||
},
|
||||
{
|
||||
title: 'Mode',
|
||||
field: 'noop',
|
||||
align: 'center',
|
||||
render: rowData =>
|
||||
rowData.noop ? (
|
||||
<Typography>NOOP</Typography>
|
||||
) : (
|
||||
<Typography>NO-NOOP</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'status',
|
||||
align: 'center',
|
||||
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 reports
|
||||
</Typography>
|
||||
}
|
||||
title={`Latest PuppetDB reports from node ${certName}`}
|
||||
columns={columns}
|
||||
data={value || []}
|
||||
isLoading={loading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { 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';
|
||||
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,52 @@
|
||||
/*
|
||||
* 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 { StatusField } from './StatusField';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('StatusField', () => {
|
||||
it('should render failed', async () => {
|
||||
const render = await renderInTestApp(<StatusField status="failed" />);
|
||||
expect(render.getByText('FAILED')).toBeInTheDocument();
|
||||
expect(
|
||||
render.container.querySelector('span[aria-label="Status error"]'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render changed', async () => {
|
||||
const render = await renderInTestApp(<StatusField status="changed" />);
|
||||
expect(render.getByText('CHANGED')).toBeInTheDocument();
|
||||
expect(
|
||||
render.container.querySelector('span[aria-label="Status running"]'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render unchanged', async () => {
|
||||
const render = await renderInTestApp(<StatusField status="unchanged" />);
|
||||
expect(render.getByText('UNCHANGED')).toBeInTheDocument();
|
||||
expect(
|
||||
render.container.querySelector('span[aria-label="Status pending"]'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render default', async () => {
|
||||
const render = await renderInTestApp(<StatusField status="successful" />);
|
||||
expect(render.getByText('SUCCESSFUL')).toBeInTheDocument();
|
||||
expect(
|
||||
render.container.querySelector('span[aria-label="Status ok"]'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { StatusField } from './StatusField';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Annotation for specifying the certificate name of a node in PuppetDB.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const ANNOTATION_PUPPET_CERTNAME = 'puppet.com/certname';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
puppetdbPlugin,
|
||||
puppetdbPlugin as plugin,
|
||||
PuppetDbPage,
|
||||
} from './plugin';
|
||||
export { Router, isPuppetDbAvailable } from './components/Router';
|
||||
export * from './routes';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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 { puppetdbPlugin } from './plugin';
|
||||
|
||||
describe('puppetdb', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(puppetdbPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { puppetDbApiRef, PuppetDbClient } from './api';
|
||||
import { puppetDbRouteRef } from './routes';
|
||||
|
||||
/**
|
||||
* Create the PuppetDB frontend plugin.
|
||||
*
|
||||
* @public
|
||||
* */
|
||||
export const puppetdbPlugin = createPlugin({
|
||||
id: 'puppetDb',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: puppetDbApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
|
||||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new PuppetDbClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a routable extension for the PuppetDB plugin content.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const PuppetDbPage = puppetdbPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'PuppetDbPage',
|
||||
component: () => import('./components/Router').then(m => m.Router),
|
||||
mountPoint: puppetDbRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2023 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 { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export const puppetDbRouteRef = createRouteRef({
|
||||
id: 'puppetdb',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const puppetDbReportRouteRef = createSubRouteRef({
|
||||
id: 'puppetdb/report',
|
||||
path: '/:hash',
|
||||
parent: puppetDbRouteRef,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user