Added DevTools plugin
Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-devtools': minor
|
||||
'@backstage/plugin-devtools-backend': minor
|
||||
'@backstage/plugin-devtools-common': minor
|
||||
---
|
||||
|
||||
Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
|
||||
@@ -29,6 +29,7 @@
|
||||
"@backstage/plugin-cloudbuild": "workspace:^",
|
||||
"@backstage/plugin-code-coverage": "workspace:^",
|
||||
"@backstage/plugin-cost-insights": "workspace:^",
|
||||
"@backstage/plugin-devtools": "workspace:^",
|
||||
"@backstage/plugin-dynatrace": "workspace:^",
|
||||
"@backstage/plugin-entity-feedback": "workspace:^",
|
||||
"@backstage/plugin-explore": "workspace:^",
|
||||
|
||||
@@ -109,6 +109,8 @@ import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts
|
||||
import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card';
|
||||
import { StackstormPage } from '@backstage/plugin-stackstorm';
|
||||
import { PuppetDbPage } from '@backstage/plugin-puppetdb';
|
||||
import { DevToolsPage } from '@backstage/plugin-devtools';
|
||||
import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
@@ -291,6 +293,9 @@ const routes = (
|
||||
<Route path="/score-board" element={<ScoreBoardPage />} />
|
||||
<Route path="/stackstorm" element={<StackstormPage />} />
|
||||
<Route path="/puppetdb" element={<PuppetDbPage />} />
|
||||
<Route path="/devtools" element={<DevToolsPage />}>
|
||||
{customDevToolsPage}
|
||||
</Route>
|
||||
</FlatRoutes>
|
||||
);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import { MyGroupsSidebarItem } from '@backstage/plugin-org';
|
||||
import { SearchModal } from '../search/SearchModal';
|
||||
import Score from '@material-ui/icons/Score';
|
||||
import { useApp } from '@backstage/core-plugin-api';
|
||||
import BuildIcon from '@material-ui/icons/Build';
|
||||
|
||||
const useSidebarLogoStyles = makeStyles({
|
||||
root: {
|
||||
@@ -176,6 +177,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
to="/settings"
|
||||
>
|
||||
<SidebarSettings />
|
||||
<SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
|
||||
</SidebarGroup>
|
||||
</Sidebar>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 {
|
||||
ConfigContent,
|
||||
ExternalDependenciesContent,
|
||||
InfoContent,
|
||||
} from '@backstage/plugin-devtools';
|
||||
import { DevToolsLayout } from '@backstage/plugin-devtools';
|
||||
import React from 'react';
|
||||
|
||||
const DevToolsPage = () => {
|
||||
return (
|
||||
<DevToolsLayout>
|
||||
<DevToolsLayout.Route path="info" title="Info">
|
||||
<InfoContent />
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="config" title="Config">
|
||||
<ConfigContent />
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route
|
||||
path="external-dependencies"
|
||||
title="External Dependencies"
|
||||
>
|
||||
<ExternalDependenciesContent />
|
||||
</DevToolsLayout.Route>
|
||||
</DevToolsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const customDevToolsPage = <DevToolsPage />;
|
||||
@@ -42,6 +42,7 @@
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@backstage/plugin-code-coverage-backend": "workspace:^",
|
||||
"@backstage/plugin-devtools-backend": "workspace:^",
|
||||
"@backstage/plugin-entity-feedback-backend": "workspace:^",
|
||||
"@backstage/plugin-events-backend": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^",
|
||||
|
||||
@@ -64,6 +64,7 @@ import playlist from './plugins/playlist';
|
||||
import adr from './plugins/adr';
|
||||
import lighthouse from './plugins/lighthouse';
|
||||
import linguist from './plugins/linguist';
|
||||
import devTools from './plugins/devtools';
|
||||
import { PluginEnvironment } from './types';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
@@ -158,8 +159,8 @@ async function main() {
|
||||
const eventsEnv = useHotMemoize(module, () => createEnv('events'));
|
||||
const exploreEnv = useHotMemoize(module, () => createEnv('explore'));
|
||||
const lighthouseEnv = useHotMemoize(module, () => createEnv('lighthouse'));
|
||||
|
||||
const linguistEnv = useHotMemoize(module, () => createEnv('linguist'));
|
||||
const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
|
||||
|
||||
const apiRouter = Router();
|
||||
apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
@@ -185,6 +186,7 @@ async function main() {
|
||||
apiRouter.use('/entity-feedback', await entityFeedback(entityFeedbackEnv));
|
||||
apiRouter.use('/adr', await adr(adrEnv));
|
||||
apiRouter.use('/linguist', await linguist(linguistEnv));
|
||||
apiRouter.use('/devtools', await devTools(devToolsEnv));
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
await lighthouse(lighthouseEnv);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { createRouter } from '@backstage/plugin-devtools-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
permissions: env.permissions,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,56 @@
|
||||
# DevTools Backend
|
||||
|
||||
Welcome to the DevTools backend plugin! This plugin provides data for the [DevTools frontend](../devtools/) features.
|
||||
|
||||
## Setup
|
||||
|
||||
Here's how to get the DevTools Backend up and running:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-devtools-backend` package to your backend:
|
||||
|
||||
```sh
|
||||
# From the Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-devtools-backend
|
||||
```
|
||||
|
||||
2. Then we will create a new file named `packages/backend/src/plugins/devtools.ts`, and add the
|
||||
following to it:
|
||||
|
||||
```ts
|
||||
import { createRouter } from '@backstage/plugin-devtools-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
permissions: env.permissions,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
import devTools from './plugins/devtools';
|
||||
// ...
|
||||
async function main() {
|
||||
// ...
|
||||
// Add this line under the other lines that follow the useHotMemoize pattern
|
||||
const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
|
||||
// ...
|
||||
// Insert this line under the other lines that add their routers to apiRouter in the same way
|
||||
apiRouter.use('/devtools', await devTools(devToolsEnv));
|
||||
```
|
||||
|
||||
4. Now run `yarn start-backend` from the repo root
|
||||
5. Finally open `http://localhost:7007/api/devtools/health` in a browser and it should return `{"status":"ok"}`
|
||||
|
||||
## Links
|
||||
|
||||
- [Frontend part of the plugin](../devtools/README.md)
|
||||
- [The Backstage homepage](https://backstage.io)
|
||||
@@ -0,0 +1,41 @@
|
||||
## API Report File for "@backstage/plugin-devtools-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Config } from '@backstage/config';
|
||||
import { ConfigInfo } from '@backstage/plugin-devtools-common';
|
||||
import { DevToolsInfo } from '@backstage/plugin-devtools-common';
|
||||
import express from 'express';
|
||||
import { ExternalDependency } from '@backstage/plugin-devtools-common';
|
||||
import { Logger } from 'winston';
|
||||
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class DevToolsBackendApi {
|
||||
constructor(logger: Logger, config: Config);
|
||||
// (undocumented)
|
||||
listConfig(): Promise<ConfigInfo>;
|
||||
// (undocumented)
|
||||
listExternalDependencyDetails(): Promise<ExternalDependency[]>;
|
||||
// (undocumented)
|
||||
listInfo(): Promise<DevToolsInfo>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
devToolsBackendApi?: DevToolsBackendApi;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
permissions: PermissionEvaluator;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 interface Config {
|
||||
/**
|
||||
* DevTools configuration.
|
||||
*/
|
||||
devTools?: {
|
||||
/**
|
||||
* External dependency configuration.
|
||||
*/
|
||||
externalDependencies?: {
|
||||
/**
|
||||
* The list of endpoints to check.
|
||||
*/
|
||||
endpoints?: Array<{
|
||||
/**
|
||||
* The name of the endpoint.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Type of check to perform; currently fetch or ping
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* The target of the endpoint; currently either a URL for fetch or server name for ping.
|
||||
*/
|
||||
target: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "@backstage/plugin-devtools-backend",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin"
|
||||
},
|
||||
"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/backend-common": "workspace:^",
|
||||
"@backstage/cli-common": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/config-loader": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/plugin-devtools-common": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@types/express": "*",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"@yarnpkg/parsers": "^3.0.0-rc.4",
|
||||
"express": "^4.18.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-fetch": "^2.6.7",
|
||||
"ping": "^0.4.1",
|
||||
"semver": "^7.3.2",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/ping": "^0.4.1",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@types/yarnpkg__lockfile": "^1.1.4",
|
||||
"msw": "^0.47.0",
|
||||
"supertest": "^6.2.4"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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 { Config, ConfigReader } from '@backstage/config';
|
||||
import { loadConfigSchema } from '@backstage/config-loader';
|
||||
import {
|
||||
PackageDependency,
|
||||
DevToolsInfo,
|
||||
ExternalDependency,
|
||||
Endpoint,
|
||||
ExternalDependencyStatus,
|
||||
ConfigInfo,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { Logger } from 'winston';
|
||||
import fetch from 'node-fetch';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import ping from 'ping';
|
||||
import os from 'os';
|
||||
import fs from 'fs-extra';
|
||||
import { Lockfile } from '../util/Lockfile';
|
||||
import { memoize } from 'lodash';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
/** @public */
|
||||
export class DevToolsBackendApi {
|
||||
public constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly config: Config,
|
||||
) {}
|
||||
|
||||
public async listExternalDependencyDetails(): Promise<ExternalDependency[]> {
|
||||
const result: ExternalDependency[] = [];
|
||||
|
||||
const endpoints = this.config.getOptional<Endpoint[]>(
|
||||
'devTools.externalDependencies.endpoints',
|
||||
);
|
||||
if (!endpoints) {
|
||||
// No external dependency endpoints configured
|
||||
return result;
|
||||
}
|
||||
for (const endpoint of endpoints) {
|
||||
this.logger?.info(
|
||||
`Checking external dependency "${endpoint.name}" at "${endpoint.target}"`,
|
||||
);
|
||||
|
||||
switch (endpoint.type) {
|
||||
case 'ping': {
|
||||
const pingResult = await this.pingExternalDependency(endpoint);
|
||||
result.push(pingResult);
|
||||
break;
|
||||
}
|
||||
case 'fetch': {
|
||||
const fetchResult = await this.fetchExternalDependency(endpoint);
|
||||
result.push(fetchResult);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async fetchExternalDependency(
|
||||
endpoint: Endpoint,
|
||||
): Promise<ExternalDependency> {
|
||||
let status;
|
||||
let error;
|
||||
|
||||
await fetch(endpoint.target)
|
||||
.then(res => {
|
||||
status =
|
||||
res.status === 200
|
||||
? ExternalDependencyStatus.healthy
|
||||
: ExternalDependencyStatus.unhealthy;
|
||||
this.logger.info(
|
||||
`Fetch for ${endpoint.name} resulted in status code "${res.status}"`,
|
||||
);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
this.logger.error(`Fetch failed for ${endpoint.name} - ${err.message}`);
|
||||
error = err.message;
|
||||
});
|
||||
|
||||
const result: ExternalDependency = {
|
||||
name: endpoint.name,
|
||||
type: endpoint.type,
|
||||
target: endpoint.target,
|
||||
status: status ?? ExternalDependencyStatus.unhealthy,
|
||||
error: error ?? undefined,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async pingExternalDependency(
|
||||
endpoint: Endpoint,
|
||||
): Promise<ExternalDependency> {
|
||||
const pingResult = await ping.promise.probe(endpoint.target);
|
||||
|
||||
let error;
|
||||
if (
|
||||
pingResult.packetLoss === '100.000' ||
|
||||
pingResult.packetLoss === 'unknown'
|
||||
) {
|
||||
this.logger.error(
|
||||
`Ping failed for ${endpoint.name} - ${pingResult.output}`,
|
||||
);
|
||||
error =
|
||||
pingResult.output === ''
|
||||
? `${endpoint.target} - Unknown`
|
||||
: pingResult.output;
|
||||
}
|
||||
|
||||
this.logger.info(`Ping results for ${endpoint.name}: ${pingResult.output}`);
|
||||
|
||||
const result: ExternalDependency = {
|
||||
name: endpoint.name,
|
||||
type: endpoint.type,
|
||||
target: endpoint.target,
|
||||
status: pingResult.alive
|
||||
? ExternalDependencyStatus.healthy
|
||||
: ExternalDependencyStatus.unhealthy,
|
||||
error: error ?? undefined,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async listConfig(): Promise<ConfigInfo> {
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const paths = findPaths(__dirname);
|
||||
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
const schemaFunc = async () => {
|
||||
return await loadConfigSchema({
|
||||
dependencies: packages.map(p => p.packageJson.name),
|
||||
});
|
||||
};
|
||||
|
||||
const schemaMemo = memoize(schemaFunc);
|
||||
const schema = await schemaMemo();
|
||||
|
||||
const configInfo: ConfigInfo = {
|
||||
config: undefined,
|
||||
error: undefined,
|
||||
};
|
||||
try {
|
||||
const config = {
|
||||
data: this.config.get() as JsonObject,
|
||||
context: 'inline',
|
||||
};
|
||||
const sanitizedConfigs = schema.process([config], {
|
||||
ignoreSchemaErrors: false,
|
||||
valueTransform: (value, context) =>
|
||||
context.visibility === 'secret' ? '<secret>' : value,
|
||||
});
|
||||
|
||||
const data = ConfigReader.fromConfigs(sanitizedConfigs).get();
|
||||
configInfo.config = data;
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
// The config is not valid for some reason but we want to be able to see it still
|
||||
const config = {
|
||||
data: this.config.get() as JsonObject,
|
||||
context: 'inline',
|
||||
};
|
||||
const sanitizedConfigs = schema.process([config], {
|
||||
ignoreSchemaErrors: true,
|
||||
valueTransform: (value, context) =>
|
||||
context.visibility === 'secret' ? '<secret>' : value,
|
||||
});
|
||||
|
||||
const data = ConfigReader.fromConfigs(sanitizedConfigs).get();
|
||||
configInfo.config = data;
|
||||
configInfo.error = {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
messages: error.messages as string[] | undefined,
|
||||
stack: error.stack,
|
||||
};
|
||||
}
|
||||
|
||||
return configInfo;
|
||||
}
|
||||
|
||||
public async listInfo(): Promise<DevToolsInfo> {
|
||||
const operatingSystem = `${os.type} ${os.release} - ${os.platform}/${os.arch}`;
|
||||
const nodeJsVersion = process.version;
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const paths = findPaths(__dirname);
|
||||
const backstageFile = paths.resolveTargetRoot('backstage.json');
|
||||
let backstageJson = undefined;
|
||||
if (fs.existsSync(backstageFile)) {
|
||||
const buffer = await fs.readFile(backstageFile);
|
||||
backstageJson = JSON.parse(buffer.toString());
|
||||
}
|
||||
|
||||
const lockfilePath = paths.resolveTargetRoot('yarn.lock');
|
||||
const lockfile = await Lockfile.load(lockfilePath);
|
||||
|
||||
const deps = [...lockfile.keys()].filter(n => n.startsWith('@backstage/'));
|
||||
|
||||
const infoDependencies: PackageDependency[] = [];
|
||||
for (const dep of deps) {
|
||||
const versions = new Set(lockfile.get(dep)!.map(i => i.version));
|
||||
const infoDependency: PackageDependency = {
|
||||
name: dep,
|
||||
versions: [...versions].join(', '),
|
||||
};
|
||||
infoDependencies.push(infoDependency);
|
||||
}
|
||||
|
||||
const info: DevToolsInfo = {
|
||||
operatingSystem: operatingSystem ?? 'N/A',
|
||||
nodeJsVersion: nodeJsVersion ?? 'N/A',
|
||||
backstageVersion:
|
||||
backstageJson && backstageJson.version ? backstageJson.version : 'N/A',
|
||||
dependencies: infoDependencies,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { DevToolsBackendApi } from './DevToolsBackendApi';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 { DevToolsBackendApi } from './api';
|
||||
export * from './service/router';
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { getRootLogger } from '@backstage/backend-common';
|
||||
import yn from 'yn';
|
||||
import { startStandaloneServer } from './service/standaloneServer';
|
||||
|
||||
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('CTRL+C pressed; exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
import { createRouter } from './router';
|
||||
|
||||
const mockedAuthorize: jest.MockedFunction<PermissionEvaluator['authorize']> =
|
||||
jest.fn();
|
||||
const mockedPermissionQuery: jest.MockedFunction<
|
||||
PermissionEvaluator['authorizeConditional']
|
||||
> = jest.fn();
|
||||
|
||||
const permissionEvaluator: PermissionEvaluator = {
|
||||
authorize: mockedAuthorize,
|
||||
authorizeConditional: mockedPermissionQuery,
|
||||
};
|
||||
|
||||
describe('createRouter', () => {
|
||||
let app: express.Express;
|
||||
|
||||
beforeAll(async () => {
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
config: new ConfigReader({
|
||||
healthCheck: {
|
||||
endpoint: [
|
||||
{
|
||||
name: '',
|
||||
type: '',
|
||||
target: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
permissions: permissionEvaluator,
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('GET /health', () => {
|
||||
it('returns ok', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ status: 'ok' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 {
|
||||
AuthorizeResult,
|
||||
PermissionEvaluator,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
devToolsConfigReadPermission,
|
||||
devToolsExternalDependenciesReadPermission,
|
||||
devToolsInfoReadPermission,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { DevToolsBackendApi } from '../api';
|
||||
import { Logger } from 'winston';
|
||||
import { NotAllowedError } from '@backstage/errors';
|
||||
import Router from 'express-promise-router';
|
||||
import { errorHandler } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
devToolsBackendApi?: DevToolsBackendApi;
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
permissions: PermissionEvaluator;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger, config, permissions } = options;
|
||||
|
||||
const devToolsBackendApi =
|
||||
options.devToolsBackendApi || new DevToolsBackendApi(logger, config);
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/health', (_req, res) => {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
|
||||
router.get('/info', async (req, response) => {
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
req.header('authorization'),
|
||||
);
|
||||
|
||||
const decision = (
|
||||
await permissions.authorize(
|
||||
[{ permission: devToolsInfoReadPermission }],
|
||||
{
|
||||
token,
|
||||
},
|
||||
)
|
||||
)[0];
|
||||
|
||||
if (decision.result === AuthorizeResult.DENY) {
|
||||
throw new NotAllowedError('Unauthorized');
|
||||
}
|
||||
|
||||
const info = await devToolsBackendApi.listInfo();
|
||||
|
||||
response.status(200).json(info);
|
||||
});
|
||||
|
||||
router.get('/config', async (req, response) => {
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
req.header('authorization'),
|
||||
);
|
||||
|
||||
const decision = (
|
||||
await permissions.authorize(
|
||||
[{ permission: devToolsConfigReadPermission }],
|
||||
{
|
||||
token,
|
||||
},
|
||||
)
|
||||
)[0];
|
||||
|
||||
if (decision.result === AuthorizeResult.DENY) {
|
||||
throw new NotAllowedError('Unauthorized');
|
||||
}
|
||||
|
||||
const configList = await devToolsBackendApi.listConfig();
|
||||
|
||||
response.status(200).json(configList);
|
||||
});
|
||||
|
||||
router.get('/external-dependencies', async (req, response) => {
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
req.header('authorization'),
|
||||
);
|
||||
|
||||
const decision = (
|
||||
await permissions.authorize(
|
||||
[{ permission: devToolsExternalDependenciesReadPermission }],
|
||||
{
|
||||
token,
|
||||
},
|
||||
)
|
||||
)[0];
|
||||
|
||||
if (decision.result === AuthorizeResult.DENY) {
|
||||
throw new NotAllowedError('Unauthorized');
|
||||
}
|
||||
|
||||
const health = await devToolsBackendApi.listExternalDependencyDetails();
|
||||
|
||||
response.status(200).json(health);
|
||||
});
|
||||
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 {
|
||||
ServerTokenManager,
|
||||
SingleHostDiscovery,
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { Server } from 'http';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { createRouter } from './router';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'devtools-backend-backend' });
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const tokenManager = ServerTokenManager.fromConfig(config, {
|
||||
logger,
|
||||
});
|
||||
const permissions = ServerPermissionClient.fromConfig(config, {
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
logger.debug('Starting application server...');
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
config,
|
||||
permissions,
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/devtools-backend', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 {};
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* 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 fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import { parseSyml, stringifySyml } from '@yarnpkg/parsers';
|
||||
import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile';
|
||||
|
||||
const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
|
||||
|
||||
type LockfileData = {
|
||||
[entry: string]: {
|
||||
version: string;
|
||||
resolved?: string;
|
||||
integrity?: string;
|
||||
dependencies?: { [name: string]: string };
|
||||
};
|
||||
};
|
||||
|
||||
type LockfileQueryEntry = {
|
||||
range: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
/** Entries that have an invalid version range, for example an npm tag */
|
||||
type AnalyzeResultInvalidRange = {
|
||||
name: string;
|
||||
range: string;
|
||||
};
|
||||
|
||||
/** Entries that can be deduplicated by bumping to an existing higher version */
|
||||
type AnalyzeResultNewVersion = {
|
||||
name: string;
|
||||
range: string;
|
||||
oldVersion: string;
|
||||
newVersion: string;
|
||||
};
|
||||
|
||||
/** Entries that would need a dependency update in package.json to be deduplicated */
|
||||
type AnalyzeResultNewRange = {
|
||||
name: string;
|
||||
oldRange: string;
|
||||
newRange: string;
|
||||
oldVersion: string;
|
||||
newVersion: string;
|
||||
};
|
||||
|
||||
type AnalyzeResult = {
|
||||
invalidRanges: AnalyzeResultInvalidRange[];
|
||||
newVersions: AnalyzeResultNewVersion[];
|
||||
newRanges: AnalyzeResultNewRange[];
|
||||
};
|
||||
|
||||
function parseLockfile(lockfileContents: string) {
|
||||
try {
|
||||
return {
|
||||
object: parseSyml(lockfileContents),
|
||||
type: 'success',
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
object: null,
|
||||
type: err,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// the new yarn header is handled out of band of the parsing
|
||||
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746
|
||||
const NEW_HEADER = `${[
|
||||
`# This file is generated by running "yarn install" inside your project.\n`,
|
||||
`# Manual changes might be lost - proceed with caution!\n`,
|
||||
].join(``)}\n`;
|
||||
|
||||
function stringifyLockfile(data: LockfileData, legacy: boolean) {
|
||||
return legacy
|
||||
? legacyStringifyLockfile(data)
|
||||
: NEW_HEADER + stringifySyml(data);
|
||||
}
|
||||
// taken from yarn parser package
|
||||
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-parsers/sources/syml.ts#L136
|
||||
const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
|
||||
|
||||
// these are special top level yarn keys.
|
||||
// https://github.com/yarnpkg/berry/blob/9bd61fbffb83d0b8166a9cc26bec3a58743aa453/packages/yarnpkg-parsers/sources/syml.ts#L9
|
||||
const SPECIAL_OBJECT_KEYS = [
|
||||
`__metadata`,
|
||||
`version`,
|
||||
`resolution`,
|
||||
`dependencies`,
|
||||
`peerDependencies`,
|
||||
`dependenciesMeta`,
|
||||
`peerDependenciesMeta`,
|
||||
`binaries`,
|
||||
];
|
||||
|
||||
export class Lockfile {
|
||||
static async load(path: string) {
|
||||
const lockfileContents = await fs.readFile(path, 'utf8');
|
||||
const legacy = LEGACY_REGEX.test(lockfileContents);
|
||||
const lockfile = parseLockfile(lockfileContents);
|
||||
if (lockfile.type !== 'success') {
|
||||
throw new Error(`Failed yarn.lock parse with ${lockfile.type}`);
|
||||
}
|
||||
|
||||
const data = lockfile.object as LockfileData;
|
||||
const packages = new Map<string, LockfileQueryEntry[]>();
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (SPECIAL_OBJECT_KEYS.includes(key)) continue;
|
||||
|
||||
const [, name, range] = ENTRY_PATTERN.exec(key) ?? [];
|
||||
if (!name) {
|
||||
throw new Error(`Failed to parse yarn.lock entry '${key}'`);
|
||||
}
|
||||
|
||||
let queries = packages.get(name);
|
||||
if (!queries) {
|
||||
queries = [];
|
||||
packages.set(name, queries);
|
||||
}
|
||||
queries.push({ range, version: value.version });
|
||||
}
|
||||
|
||||
return new Lockfile(path, packages, data, legacy);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly path: string,
|
||||
private readonly packages: Map<string, LockfileQueryEntry[]>,
|
||||
private readonly data: LockfileData,
|
||||
private readonly legacy: boolean = false,
|
||||
) {}
|
||||
|
||||
/** Get the entries for a single package in the lockfile */
|
||||
get(name: string): LockfileQueryEntry[] | undefined {
|
||||
return this.packages.get(name);
|
||||
}
|
||||
|
||||
/** Returns the name of all packages available in the lockfile */
|
||||
keys(): IterableIterator<string> {
|
||||
return this.packages.keys();
|
||||
}
|
||||
|
||||
/** Analyzes the lockfile to identify possible actions and warnings for the entries */
|
||||
analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult {
|
||||
const { filter } = options ?? {};
|
||||
const result: AnalyzeResult = {
|
||||
invalidRanges: [],
|
||||
newVersions: [],
|
||||
newRanges: [],
|
||||
};
|
||||
|
||||
for (const [name, allEntries] of this.packages) {
|
||||
if (filter && !filter(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get rid of and signal any invalid ranges upfront
|
||||
const invalid = allEntries.filter(e => !semver.validRange(e.range));
|
||||
result.invalidRanges.push(
|
||||
...invalid.map(({ range }) => ({ name, range })),
|
||||
);
|
||||
|
||||
// Grab all valid entries, if there aren't at least 2 different valid ones we're done
|
||||
const entries = allEntries.filter(e => semver.validRange(e.range));
|
||||
if (entries.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find all versions currently in use
|
||||
const versions = Array.from(new Set(entries.map(e => e.version))).sort(
|
||||
(v1, v2) => semver.rcompare(v1, v2),
|
||||
);
|
||||
|
||||
// If we're not using at least 2 different versions we're done
|
||||
if (versions.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const acceptedVersions = new Set<string>();
|
||||
for (const { version, range } of entries) {
|
||||
// Finds the highest matching version from the the known versions
|
||||
// TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one
|
||||
const acceptedVersion = versions.find(v => semver.satisfies(v, range));
|
||||
if (!acceptedVersion) {
|
||||
throw new Error(
|
||||
`No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (acceptedVersion !== version) {
|
||||
result.newVersions.push({
|
||||
name,
|
||||
range,
|
||||
newVersion: acceptedVersion,
|
||||
oldVersion: version,
|
||||
});
|
||||
}
|
||||
|
||||
acceptedVersions.add(acceptedVersion);
|
||||
}
|
||||
|
||||
// If all ranges were able to accept the same version, we're done
|
||||
if (acceptedVersions.size === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the max version that we may want bump older packages to
|
||||
const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0];
|
||||
// Find all existing ranges that satisfy the new max version, and pick the one that
|
||||
// results in the highest minimum allowed version, usually being the more specific one
|
||||
const maxEntry = entries
|
||||
.filter(e => semver.satisfies(maxVersion, e.range))
|
||||
.map(e => ({ e, min: semver.minVersion(e.range) }))
|
||||
.filter(p => p.min)
|
||||
.sort((a, b) => semver.rcompare(a.min!, b.min!))[0]?.e;
|
||||
if (!maxEntry) {
|
||||
throw new Error(
|
||||
`No entry found that satisfies max version '${maxVersion}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Find all entries that don't satisfy the max version
|
||||
for (const { version, range } of entries) {
|
||||
if (semver.satisfies(maxVersion, range)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.newRanges.push({
|
||||
name,
|
||||
oldRange: range,
|
||||
newRange: maxEntry.range,
|
||||
oldVersion: version,
|
||||
newVersion: maxVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
remove(name: string, range: string): boolean {
|
||||
const query = `${name}@${range}`;
|
||||
const existed = Boolean(this.data[query]);
|
||||
delete this.data[query];
|
||||
|
||||
const newEntries = this.packages.get(name)?.filter(e => e.range !== range);
|
||||
if (newEntries) {
|
||||
this.packages.set(name, newEntries);
|
||||
}
|
||||
|
||||
return existed;
|
||||
}
|
||||
|
||||
/** Modifies the lockfile by bumping packages to the suggested versions */
|
||||
replaceVersions(results: AnalyzeResultNewVersion[]) {
|
||||
for (const { name, range, oldVersion, newVersion } of results) {
|
||||
const query = `${name}@${range}`;
|
||||
|
||||
// Update the backing data
|
||||
const entryData = this.data[query];
|
||||
if (!entryData) {
|
||||
throw new Error(`No entry data for ${query}`);
|
||||
}
|
||||
if (entryData.version !== oldVersion) {
|
||||
throw new Error(
|
||||
`Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Modifying the data in the entry is not enough, we need to reference an existing version object
|
||||
const matchingEntry = Object.entries(this.data).find(
|
||||
([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion,
|
||||
);
|
||||
if (!matchingEntry) {
|
||||
throw new Error(
|
||||
`No matching entry found for ${name} at version ${newVersion}`,
|
||||
);
|
||||
}
|
||||
this.data[query] = matchingEntry[1];
|
||||
|
||||
// Update our internal data structure
|
||||
const entry = this.packages.get(name)?.find(e => e.range === range);
|
||||
if (!entry) {
|
||||
throw new Error(`No entry data for ${query}`);
|
||||
}
|
||||
if (entry.version !== oldVersion) {
|
||||
throw new Error(
|
||||
`Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
|
||||
);
|
||||
}
|
||||
entry.version = newVersion;
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
await fs.writeFile(this.path, this.toString(), 'utf8');
|
||||
}
|
||||
|
||||
toString() {
|
||||
return stringifyLockfile(this.data, this.legacy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,3 @@
|
||||
# DevTools Common
|
||||
|
||||
Common types and permissions for the DevTools plugin.
|
||||
@@ -0,0 +1,74 @@
|
||||
## API Report File for "@backstage/plugin-devtools-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BasicPermission } from '@backstage/plugin-permission-common';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
// @public (undocumented)
|
||||
export type ConfigError = {
|
||||
name: string;
|
||||
message: string;
|
||||
messages?: string[];
|
||||
stack?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ConfigInfo = {
|
||||
config?: JsonValue;
|
||||
error?: ConfigError;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsAdministerPermission: BasicPermission;
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsConfigReadPermission: BasicPermission;
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsExternalDependenciesReadPermission: BasicPermission;
|
||||
|
||||
// @public (undocumented)
|
||||
export type DevToolsInfo = {
|
||||
operatingSystem: string;
|
||||
nodeJsVersion: string;
|
||||
backstageVersion: string;
|
||||
dependencies: PackageDependency[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsInfoReadPermission: BasicPermission;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Endpoint = {
|
||||
name: string;
|
||||
type: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ExternalDependency = {
|
||||
name: string;
|
||||
type: string;
|
||||
target: string;
|
||||
status: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export enum ExternalDependencyStatus {
|
||||
// (undocumented)
|
||||
healthy = 'Healthy',
|
||||
// (undocumented)
|
||||
unhealthy = 'Unhealthy',
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type PackageDependency = {
|
||||
name: string;
|
||||
versions: string;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@backstage/plugin-devtools-common",
|
||||
"description": "Common functionalities for the devtools plugin",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "common-library"
|
||||
},
|
||||
"scripts": {
|
||||
"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/plugin-permission-common": "workspace:^",
|
||||
"@backstage/types": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 * from './types';
|
||||
export * from './permissions';
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 { createPermission } from '@backstage/plugin-permission-common';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const devToolsAdministerPermission = createPermission({
|
||||
name: 'devtools.administer',
|
||||
attributes: { action: 'read' },
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const devToolsInfoReadPermission = createPermission({
|
||||
name: 'devtools.info',
|
||||
attributes: { action: 'read' },
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const devToolsConfigReadPermission = createPermission({
|
||||
name: 'devtools.config',
|
||||
attributes: { action: 'read' },
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const devToolsExternalDependenciesReadPermission = createPermission({
|
||||
name: 'devtools.external-dependencies',
|
||||
attributes: { action: 'read' },
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 {};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 { JsonValue } from '@backstage/types';
|
||||
|
||||
/** @public */
|
||||
export type Endpoint = {
|
||||
name: string;
|
||||
type: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type ExternalDependency = {
|
||||
name: string;
|
||||
type: string;
|
||||
target: string;
|
||||
status: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type DevToolsInfo = {
|
||||
operatingSystem: string;
|
||||
nodeJsVersion: string;
|
||||
backstageVersion: string;
|
||||
dependencies: PackageDependency[];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type PackageDependency = {
|
||||
name: string;
|
||||
versions: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export enum ExternalDependencyStatus {
|
||||
healthy = 'Healthy',
|
||||
unhealthy = 'Unhealthy',
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type ConfigInfo = {
|
||||
config?: JsonValue;
|
||||
error?: ConfigError;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type ConfigError = {
|
||||
name: string;
|
||||
message: string;
|
||||
messages?: string[];
|
||||
stack?: string;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,375 @@
|
||||
# DevTools
|
||||
|
||||
Welcome to the DevTools plugin!
|
||||
|
||||
## Features
|
||||
|
||||
The DevTools plugin comes with two tabs out of the box.
|
||||
|
||||
### Info
|
||||
|
||||
Lists helpful information about your current running Backstage instance such as: OS, NodeJS version, Backstage version, and package versions.
|
||||
|
||||

|
||||
|
||||
### Config
|
||||
|
||||
Lists the configuration being used by your current running Backstage instance.
|
||||
|
||||

|
||||
|
||||
## Optional Features
|
||||
|
||||
The DevTools plugin can be setup with other tabs with additional helpful features.
|
||||
|
||||
### External Dependencies
|
||||
|
||||
Lists the status of configured External Dependencies based on your current running Backstage instance's ability to reach them
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
|
||||
The following sections will help you get the DevTools plugin setup and running.
|
||||
|
||||
### Backend
|
||||
|
||||
You need to setup the [DevTools backend plugin](../devtools-backend/README.md) before you move forward with any of the following steps if you haven't already.
|
||||
|
||||
### Frontend
|
||||
|
||||
To setup the DevTools frontend you'll need to do the following steps:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-devtools` package to your frontend app:
|
||||
|
||||
```sh
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/app @backstage/plugin-devtools
|
||||
```
|
||||
|
||||
2. Now open the `packages/app/src/App.tsx` file
|
||||
3. Then after all the import statements add the following line:
|
||||
|
||||
```ts
|
||||
import { DevToolsPage } from '@backstage/plugin-devtools';
|
||||
```
|
||||
|
||||
4. In this same file just before the closing `</ FlatRoutes>`, this will be near the bottom of the file, add this line:
|
||||
|
||||
```ts
|
||||
<Route path="/devtools" element={<DevToolsPage />} />
|
||||
```
|
||||
|
||||
5. Next open the `packages/app/src/components/Root/Root.tsx` file
|
||||
6. We want to add this icon import after all the existing import statements:
|
||||
|
||||
```ts
|
||||
import BuildIcon from '@material-ui/icons/Build';
|
||||
```
|
||||
|
||||
7. Then add this line just after the `<SidebarSettings />` line:
|
||||
|
||||
```ts
|
||||
<SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
|
||||
```
|
||||
|
||||
8. Now run `yarn dev` from the root of your project and you should see the DevTools option show up just below Settings in your sidebar and clicking on it will get you to the [Info tab](#info)
|
||||
|
||||
## Customizing
|
||||
|
||||
The DevTools plugin has been designed so that you can customize the tabs to suite your needs. You may only want some or none of the out of the box tabs or you may want to add your own. The following sections explains how to do that (assuming you've already done the [setup steps](#setup)). As part of this example we'll also be showing how you can add the optional [External Dependencies](#external-dependencies) tab.
|
||||
|
||||
1. In the `packages/app/src/components` folder create a new sub-folder called `devtools`
|
||||
2. Then in this new `devtools` folder add a file called `CustomDevToolsPage.tsx`
|
||||
3. In the `CustomDevToolsPage.tsx` file add the following content:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ConfigContent,
|
||||
ExternalDependenciesContent,
|
||||
InfoContent,
|
||||
} from '@backstage/plugin-devtools';
|
||||
import { DevToolsLayout } from '@backstage/plugin-devtools';
|
||||
import React from 'react';
|
||||
|
||||
export const DevToolsPage = () => {
|
||||
return (
|
||||
<DevToolsLayout>
|
||||
<DevToolsLayout.Route path="info" title="Info">
|
||||
<InfoContent />
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="config" title="Config">
|
||||
<ConfigContent />
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route
|
||||
path="external-dependencies"
|
||||
title="External Dependencies"
|
||||
>
|
||||
<ExternalDependenciesContent />
|
||||
</DevToolsLayout.Route>
|
||||
</DevToolsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const customDevToolsPage = <DevToolsPage />;
|
||||
```
|
||||
|
||||
4. Now open the `packages/app/src/App.tsx` file and add the following import after all the existing import statements:
|
||||
|
||||
```ts
|
||||
import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
|
||||
```
|
||||
|
||||
5. Then we need to adjust our route as follows
|
||||
|
||||
```diff
|
||||
- <Route path="/devtools" element={<DevToolsPage />} />
|
||||
+ <Route path="/devtools" element={<DevToolsPage />} >
|
||||
+ {customDevToolsPage}
|
||||
+ </Route>
|
||||
```
|
||||
|
||||
6. Now run `yarn dev` from the root of your project. When you go to the DevTools you'll now see you have a third tab for [External Dependencies](#external-dependencies)
|
||||
|
||||
With this setup you can add or remove the tabs as you'd like or add your own simply by editing your `CustomDevToolsPage.tsx` file
|
||||
|
||||
## Permissions
|
||||
|
||||
The DevTools plugin supports the [permissions framework](https://backstage.io/docs/permissions/overview), the following sections outline how you can use them with the assumption that you have the permissions framework setup and working.
|
||||
|
||||
**Note:** These sections are intended as guidance and are completely optional. The DevTools plugin will work with the permission framework off or on without any specific policy setup.
|
||||
|
||||
### Secure Sidebar Option
|
||||
|
||||
To use the permission framework to secure the DevTools sidebar option you'll want to do the following:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-devtools-common` package to your frontend app:
|
||||
|
||||
```sh
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/app @backstage/plugin-devtools
|
||||
```
|
||||
|
||||
2. Then open the `packages/app/src/components/Root/Root.tsx` file
|
||||
3. The add these imports after all the existing import statements:
|
||||
|
||||
```ts
|
||||
import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
```
|
||||
|
||||
4. Then make the following change:
|
||||
|
||||
```diff
|
||||
- <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
|
||||
+ <RequirePermission
|
||||
+ permission={devToolsAdministerPermission}
|
||||
+ errorPage={<></>}>
|
||||
+ <SidebarItem icon={BuildIcon} to="devtools" text="DevTools" />
|
||||
+ </RequirePermission>
|
||||
```
|
||||
|
||||
### Secure the DevTools Route
|
||||
|
||||
To use the permission framework to secure the DevTools route you'll want to do the following:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-devtools-common` package to your frontend app (skip this step if you've already done this):
|
||||
|
||||
```sh
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/app @backstage/plugin-devtools-common
|
||||
```
|
||||
|
||||
2. Then open the `packages/app/src/App.tsx` file
|
||||
3. The add this import after all the existing import statements:
|
||||
|
||||
```ts
|
||||
import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
|
||||
```
|
||||
|
||||
4. Then make the following change:
|
||||
|
||||
```diff
|
||||
- <Route path="/devtools" element={<DevToolsPage />} />
|
||||
+ <Route path="/devtools"
|
||||
+ element={
|
||||
+ <RequirePermission permission={devToolsAdministerPermission}>
|
||||
+ <DevToolsPage />
|
||||
+ </RequirePermission>
|
||||
+ }
|
||||
+ />
|
||||
```
|
||||
|
||||
Note: if you are using a `customDevToolsPage` as per the [Customizing](#customizing) documentation the changes for Step 4 will be:
|
||||
|
||||
```diff
|
||||
- <Route path="/devtools" element={<DevToolsPage />} />
|
||||
+ <Route path="/devtools"
|
||||
+ element={
|
||||
+ <RequirePermission permission={devToolsAdministerPermission}>
|
||||
+ <DevToolsPage />
|
||||
+ </RequirePermission>
|
||||
+ }
|
||||
+ >
|
||||
+ {customDevToolsPage}
|
||||
+ </Route>
|
||||
```
|
||||
|
||||
### Permission Policy
|
||||
|
||||
Here is an example permission policy that you might use to secure the DevTools plugin:
|
||||
|
||||
```ts
|
||||
// packages/backend/src/plugins/permission.ts
|
||||
|
||||
class TestPermissionPolicy implements PermissionPolicy {
|
||||
async handle(request: PolicyQuery): Promise<PolicyDecision> {
|
||||
if (isPermission(request.permission, devToolsAdministerPermission)) {
|
||||
if (
|
||||
user?.identity.ownershipEntityRefs.includes(
|
||||
'group:default/backstage-admins',
|
||||
)
|
||||
) {
|
||||
return { result: AuthorizeResult.ALLOW };
|
||||
}
|
||||
return { result: AuthorizeResult.DENY };
|
||||
}
|
||||
|
||||
if (isPermission(request.permission, devToolsInfoReadPermission)) {
|
||||
if (
|
||||
user?.identity.ownershipEntityRefs.includes(
|
||||
'group:default/backstage-admins',
|
||||
)
|
||||
) {
|
||||
return { result: AuthorizeResult.ALLOW };
|
||||
}
|
||||
return { result: AuthorizeResult.DENY };
|
||||
}
|
||||
|
||||
if (isPermission(request.permission, devToolsConfigReadPermission)) {
|
||||
if (
|
||||
user?.identity.ownershipEntityRefs.includes(
|
||||
'group:default/backstage-admins',
|
||||
)
|
||||
) {
|
||||
return { result: AuthorizeResult.ALLOW };
|
||||
}
|
||||
return { result: AuthorizeResult.DENY };
|
||||
}
|
||||
|
||||
if (
|
||||
isPermission(
|
||||
request.permission,
|
||||
devToolsExternalDependenciesReadPermission,
|
||||
)
|
||||
) {
|
||||
if (
|
||||
user?.identity.ownershipEntityRefs.includes(
|
||||
'group:default/backstage-admins',
|
||||
)
|
||||
) {
|
||||
return { result: AuthorizeResult.ALLOW };
|
||||
}
|
||||
return { result: AuthorizeResult.DENY };
|
||||
}
|
||||
|
||||
return { result: AuthorizeResult.ALLOW };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To use this policy you'll need to make sure to add the `@backstage/plugin-devtools-common` package to your backend you can do that by running this command:
|
||||
|
||||
```sh
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-devtools-common
|
||||
```
|
||||
|
||||
You'll also need to add these imports:
|
||||
|
||||
```ts
|
||||
import {
|
||||
devToolsAdministerPermission,
|
||||
devToolsConfigReadPermission,
|
||||
devToolsExternalDependenciesReadPermission,
|
||||
devToolsInfoReadPermission,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
```
|
||||
|
||||
**Note:** The group "group:default/backstage-admins" is simply an example and does not exist. You can point this to any group you have in your catalog instead.
|
||||
|
||||
### Customizing with Permissions
|
||||
|
||||
If you followed the [Customizing](#customizing) documentation and want to use permission there this is what your `CustomDevToolsPage.tsx` would look like:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ConfigContent,
|
||||
ExternalDependenciesContent,
|
||||
InfoContent,
|
||||
} from '@backstage/plugin-devtools';
|
||||
import { DevToolsLayout } from '@backstage/plugin-devtools';
|
||||
import {
|
||||
devToolsConfigReadPermission,
|
||||
devToolsExternalDependenciesReadPermission,
|
||||
devToolsInfoReadPermission,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import React from 'react';
|
||||
|
||||
const DevToolsPage = () => {
|
||||
return (
|
||||
<DevToolsLayout>
|
||||
<DevToolsLayout.Route path="info" title="Info">
|
||||
<RequirePermission permission={devToolsInfoReadPermission}>
|
||||
<InfoContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="config" title="Config">
|
||||
<RequirePermission permission={devToolsConfigReadPermission}>
|
||||
<ConfigContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route
|
||||
path="external-dependencies"
|
||||
title="External Dependencies"
|
||||
>
|
||||
<RequirePermission
|
||||
permission={devToolsExternalDependenciesReadPermission}
|
||||
>
|
||||
<ExternalDependenciesContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
</DevToolsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const customDevToolsPage = <DevToolsPage />;
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The following sections outline the configuration for the DevTools plugin
|
||||
|
||||
### External Dependencies Configuration
|
||||
|
||||
If you decide to use the External Dependencies tab then you'll need to setup the configuration for it in your `app-config.yaml`, if there is no config setup then the tab will be empty. Here's an example:
|
||||
|
||||
```yaml
|
||||
devTools:
|
||||
externalDependencies:
|
||||
endpoints:
|
||||
- name: 'Google'
|
||||
type: 'fetch'
|
||||
target: 'https://google.ca'
|
||||
- name: 'Google Public DNS'
|
||||
type: 'ping'
|
||||
target: '8.8.8.8'
|
||||
```
|
||||
|
||||
Configuration details:
|
||||
|
||||
- `endpoints` is an array
|
||||
- `name` is the friendly name for your endpoint
|
||||
- `type` can be either `ping` or `fetch` and will perform the respective action on the `target`
|
||||
- `target` is either a URL or server that you want to trigger a `type` action on
|
||||
@@ -0,0 +1,59 @@
|
||||
## API Report File for "@backstage/plugin-devtools"
|
||||
|
||||
> 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 { default as default_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { TabProps } from '@material-ui/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const ConfigContent: () => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const DevToolsLayout: {
|
||||
({ children }: DevToolsLayoutProps): JSX.Element;
|
||||
Route: (props: SubRoute) => null;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type DevToolsLayoutProps = {
|
||||
children?: default_2.ReactNode;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const DevToolsPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsPlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ExternalDependenciesContent: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const InfoContent: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SubRoute = {
|
||||
path: string;
|
||||
title: string;
|
||||
children: JSX.Element;
|
||||
tabProps?: TabProps<
|
||||
default_2.ElementType,
|
||||
{
|
||||
component?: default_2.ElementType;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { devToolsPlugin, DevToolsPage } from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(devToolsPlugin)
|
||||
.addPage({
|
||||
element: <DevToolsPage />,
|
||||
title: 'Root Page',
|
||||
path: '/devtools',
|
||||
})
|
||||
.render();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "@backstage/plugin-devtools",
|
||||
"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/devtools"
|
||||
},
|
||||
"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/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-devtools-common": "workspace:^",
|
||||
"@backstage/plugin-permission-react": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
"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": "*",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.47.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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';
|
||||
import {
|
||||
ConfigInfo,
|
||||
DevToolsInfo,
|
||||
ExternalDependency,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
|
||||
export const devToolsApiRef = createApiRef<DevToolsApi>({
|
||||
id: 'plugin.devtools.service',
|
||||
});
|
||||
|
||||
export interface DevToolsApi {
|
||||
getConfig(): Promise<ConfigInfo | undefined>;
|
||||
getExternalDependencies(): Promise<ExternalDependency[] | undefined>;
|
||||
getInfo(): Promise<DevToolsInfo | undefined>;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ConfigInfo,
|
||||
DevToolsInfo,
|
||||
ExternalDependency,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { DevToolsApi } from './DevToolsApi';
|
||||
|
||||
export class DevToolsClient implements DevToolsApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly identityApi: IdentityApi;
|
||||
|
||||
public constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
}
|
||||
|
||||
public async getConfig(): Promise<ConfigInfo | undefined> {
|
||||
const urlSegment = 'config';
|
||||
|
||||
const configInfo = await this.get<ConfigInfo | undefined>(urlSegment);
|
||||
return configInfo;
|
||||
}
|
||||
|
||||
public async getExternalDependencies(): Promise<
|
||||
ExternalDependency[] | undefined
|
||||
> {
|
||||
const urlSegment = 'external-dependencies';
|
||||
|
||||
const externalDependencies = await this.get<
|
||||
ExternalDependency[] | undefined
|
||||
>(urlSegment);
|
||||
return externalDependencies;
|
||||
}
|
||||
|
||||
public async getInfo(): Promise<DevToolsInfo | undefined> {
|
||||
const urlSegment = 'info';
|
||||
|
||||
const info = await this.get<DevToolsInfo | undefined>(urlSegment);
|
||||
return info;
|
||||
}
|
||||
|
||||
private async get<T>(path: string): Promise<T> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('devtools')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 * from './DevToolsApi';
|
||||
export * from './DevToolsClient';
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 { Progress, WarningPanel } from '@backstage/core-components';
|
||||
import {
|
||||
Box,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { useConfig } from '../../../hooks';
|
||||
import { ConfigError } from '@backstage/plugin-devtools-common';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
warningStyle: {
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
paperStyle: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const WarningContent = ({ error }: { error: ConfigError }) => {
|
||||
if (!error.messages) {
|
||||
return <Typography>{error.message}</Typography>;
|
||||
}
|
||||
|
||||
const messages = error.messages as string[];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{messages.map(message => (
|
||||
<Typography>{message}</Typography>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const ConfigContent = () => {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const { configInfo, loading, error } = useConfig();
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
}
|
||||
|
||||
if (!configInfo) {
|
||||
return <Alert severity="error">Unable to load config data</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{configInfo && configInfo.error && (
|
||||
<Box className={classes.warningStyle}>
|
||||
<WarningPanel title="Config validation failed">
|
||||
<WarningContent error={configInfo.error} />
|
||||
</WarningPanel>
|
||||
</Box>
|
||||
)}
|
||||
<Paper className={classes.paperStyle}>
|
||||
<ReactJson
|
||||
src={configInfo.config as object}
|
||||
name="config"
|
||||
enableClipboard={false}
|
||||
theme={theme.palette.type === 'dark' ? 'monokai' : 'rjv-default'}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { ConfigContent } from './ConfigContent';
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 {
|
||||
Progress,
|
||||
StatusError,
|
||||
StatusOK,
|
||||
StatusWarning,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { ExternalDependency } from '@backstage/plugin-devtools-common';
|
||||
import {
|
||||
Box,
|
||||
createStyles,
|
||||
Grid,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
import { useExternalDependencies } from '../../../hooks';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
paperStyle: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const getExternalDependencyStatus = (
|
||||
result: Partial<ExternalDependency> | undefined,
|
||||
) => {
|
||||
switch (result?.status) {
|
||||
case 'Healthy':
|
||||
return (
|
||||
<Typography component="span">
|
||||
<StatusOK /> {result.status}
|
||||
</Typography>
|
||||
);
|
||||
case 'Unhealthy':
|
||||
return (
|
||||
<Typography component="span">
|
||||
<StatusError /> {`${result.status}`}
|
||||
</Typography>
|
||||
);
|
||||
case undefined:
|
||||
default:
|
||||
return (
|
||||
<Typography component="span">
|
||||
<StatusWarning /> Unknown
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'Name',
|
||||
width: 'auto',
|
||||
field: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Target',
|
||||
width: 'auto',
|
||||
field: 'target',
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
width: 'auto',
|
||||
field: 'type',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
width: 'auto',
|
||||
render: (row: Partial<ExternalDependency>) => (
|
||||
<Grid container direction="column">
|
||||
<Grid item>
|
||||
<Typography variant="button">
|
||||
{getExternalDependencyStatus(row)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>{row.error && <Typography>{row.error}</Typography>}</Grid>
|
||||
</Grid>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
/** @public */
|
||||
export const ExternalDependenciesContent = () => {
|
||||
const classes = useStyles();
|
||||
const { externalDependencies, loading, error } = useExternalDependencies();
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
}
|
||||
|
||||
if (!externalDependencies || externalDependencies.length === 0) {
|
||||
return (
|
||||
<Box>
|
||||
<Paper className={classes.paperStyle}>
|
||||
<Typography>No external dependencies found</Typography>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
title="Status"
|
||||
options={{
|
||||
paging: true,
|
||||
pageSize: 20,
|
||||
pageSizeOptions: [20, 50, 100],
|
||||
loadingType: 'linear',
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
columns={columns}
|
||||
data={externalDependencies || []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { ExternalDependenciesContent } from './ExternalDependenciesContent';
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const BackstageLogoIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 337.46 428.5">
|
||||
<path d="M303 166.05a80.69 80.69 0 0013.45-10.37c.79-.77 1.55-1.53 2.3-2.3a83.12 83.12 0 007.93-9.38 63.69 63.69 0 006.32-10.77 48.58 48.58 0 004.35-16.4c1.49-19.39-10-38.67-35.62-54.22L198.56 0 78.3 115.23 0 190.25l108.6 65.91a111.59 111.59 0 0057.76 16.41c24.92 0 48.8-8.8 66.42-25.69 19.16-18.36 25.52-42.12 13.7-61.87a49.22 49.22 0 00-6.8-8.87 89.17 89.17 0 0019.32 2.15h.15a85.08 85.08 0 0031-5.79 80.88 80.88 0 0012.85-6.45zm-100.55 59.81c-19.32 18.51-50.4 21.23-75.7 5.9l-75.14-45.61 67.45-64.64 76.41 46.38c27.53 16.69 26.02 39.72 6.98 57.97zm8.93-82.22l-70.65-42.89L205.14 39l69.37 42.1c25.94 15.72 29.31 37 10.55 55a60.69 60.69 0 01-73.68 7.54zm29.86 190c-19.57 18.75-46.17 29.09-74.88 29.09a123.73 123.73 0 01-64.1-18.2L0 282.52v24.67l108.6 65.91a111.6 111.6 0 0057.76 16.42c24.92 0 48.8-8.81 66.42-25.69 12.88-12.34 20-27.13 19.68-41.49v-1.79a87.27 87.27 0 01-11.22 13.13zm0-39c-19.57 18.75-46.17 29.08-74.88 29.08a123.81 123.81 0 01-64.1-18.19L0 243.53v24.68l108.6 65.91a111.6 111.6 0 0057.76 16.42c24.92 0 48.8-8.81 66.42-25.69 12.88-12.34 20-27.13 19.68-41.5v-1.78a87.27 87.27 0 01-11.22 13.13zm0-39c-19.57 18.76-46.17 29.09-74.88 29.09a123.81 123.81 0 01-64.1-18.19L0 204.55v24.68l108.6 65.91a111.59 111.59 0 0057.76 16.41c24.92 0 48.8-8.8 66.42-25.68 12.88-12.35 20-27.13 19.68-41.5v-1.82a86.09 86.09 0 01-11.22 13.16zm83.7 25.74a94.15 94.15 0 01-60.2 25.86V334a81.6 81.6 0 0051.74-22.37c14-13.38 21.14-28.11 21-42.64v-2.19a94.92 94.92 0 01-12.54 14.65zm-83.7 91.21c-19.57 18.76-46.17 29.09-74.88 29.09a123.73 123.73 0 01-64.1-18.2L0 321.5v24.68l108.6 65.9a111.6 111.6 0 0057.76 16.42c24.92 0 48.8-8.8 66.42-25.69 12.88-12.34 20-27.13 19.68-41.49v-1.79a86.29 86.29 0 01-11.22 13.13zM327 162.45c-.68.69-1.35 1.38-2.05 2.06a94.37 94.37 0 01-10.64 8.65 91.35 91.35 0 01-11.6 7 94.53 94.53 0 01-26.24 8.71 97.69 97.69 0 01-14.16 1.57c.5 1.61.9 3.25 1.25 4.9a53.27 53.27 0 011.14 12V217h.05a84.41 84.41 0 0025.35-5.55 81 81 0 0026.39-16.82c.8-.77 1.5-1.56 2.26-2.34a82.08 82.08 0 007.93-9.38 63.76 63.76 0 006.32-10.74 48.55 48.55 0 004.32-16.45c.09-1.23.2-2.47.19-3.7V150q-1.08 1.54-2.25 3.09a96.73 96.73 0 01-8.26 9.36zm0 77.92c-.69.7-1.31 1.41-2 2.1a94.2 94.2 0 01-60.2 25.86V295a81.6 81.6 0 0051.74-22.37 73.51 73.51 0 0016.46-22.5 48.56 48.56 0 004.32-16.44c.09-1.24.2-2.47.19-3.71v-2.19c-.74 1.07-1.46 2.15-2.27 3.21a95.68 95.68 0 01-8.24 9.37zm0-39c-.69.7-1.31 1.41-2 2.1a93.18 93.18 0 01-10.63 8.65 91.63 91.63 0 01-11.63 7 95.47 95.47 0 01-37.94 10.18V256a81.65 81.65 0 0051.74-22.37c.8-.77 1.5-1.56 2.26-2.34a82.08 82.08 0 007.93-9.38 63.76 63.76 0 006.27-10.76 48.56 48.56 0 004.32-16.44c.09-1.24.2-2.48.19-3.71v-2.2c-.74 1.08-1.46 2.16-2.27 3.22a95.68 95.68 0 01-8.24 9.37z" />
|
||||
</SvgIcon>
|
||||
);
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 { Progress } from '@backstage/core-components';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
createStyles,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
import { useInfo } from '../../../hooks';
|
||||
import { InfoDependenciesTable } from './InfoDependenciesTable';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import DeveloperBoardIcon from '@material-ui/icons/DeveloperBoard';
|
||||
import { BackstageLogoIcon } from './BackstageLogoIcon';
|
||||
import FileCopyIcon from '@material-ui/icons/FileCopy';
|
||||
import { DevToolsInfo } from '@backstage/plugin-devtools-common';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
paperStyle: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
flexContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
padding: 0,
|
||||
},
|
||||
copyButton: {
|
||||
float: 'left',
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const copyToClipboard = ({ about }: { about: DevToolsInfo | undefined }) => {
|
||||
if (about) {
|
||||
let formatted = `OS: ${about.operatingSystem}\nnode: ${about.nodeJsVersion}\nbackstage: ${about.backstageVersion}\nDependencies:\n`;
|
||||
const deps = about.dependencies;
|
||||
for (const key in deps) {
|
||||
if (Object.prototype.hasOwnProperty.call(deps, key)) {
|
||||
formatted = `${formatted} ${deps[key].name}: ${deps[key].versions}\n`;
|
||||
}
|
||||
}
|
||||
window.navigator.clipboard.writeText(formatted);
|
||||
}
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const InfoContent = () => {
|
||||
const classes = useStyles();
|
||||
const { about, loading, error } = useInfo();
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Paper className={classes.paperStyle}>
|
||||
<List className={classes.flexContainer}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DeveloperBoardIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Operating System"
|
||||
secondary={about?.operatingSystem}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DescriptionIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="NodeJS Version"
|
||||
secondary={about?.nodeJsVersion}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<BackstageLogoIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Backstage Version"
|
||||
secondary={about?.backstageVersion}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider orientation="vertical" variant="middle" flexItem />
|
||||
<ListItem
|
||||
button
|
||||
onClick={() => {
|
||||
copyToClipboard({ about });
|
||||
}}
|
||||
className={classes.copyButton}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<FileCopyIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Copy Info to Clipboard" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
<InfoDependenciesTable infoDependencies={about?.dependencies} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 { Table, TableColumn } from '@backstage/core-components';
|
||||
import { PackageDependency } from '@backstage/plugin-devtools-common';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'Name',
|
||||
width: 'auto',
|
||||
field: 'name',
|
||||
defaultSort: 'asc',
|
||||
},
|
||||
{
|
||||
title: 'Versions',
|
||||
width: 'auto',
|
||||
field: 'versions',
|
||||
},
|
||||
];
|
||||
|
||||
export const InfoDependenciesTable = ({
|
||||
infoDependencies,
|
||||
}: {
|
||||
infoDependencies: PackageDependency[] | undefined;
|
||||
}) => {
|
||||
return (
|
||||
<Table
|
||||
title="Package Dependencies"
|
||||
options={{
|
||||
paging: true,
|
||||
pageSize: 15,
|
||||
pageSizeOptions: [15, 30, 100],
|
||||
loadingType: 'linear',
|
||||
padding: 'dense',
|
||||
}}
|
||||
columns={columns}
|
||||
data={infoDependencies || []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { InfoContent } from './InfoContent';
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 * from './ConfigContent';
|
||||
export * from './InfoContent';
|
||||
export * from './ExternalDependenciesContent';
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 {
|
||||
devToolsConfigReadPermission,
|
||||
devToolsInfoReadPermission,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
|
||||
import { ConfigContent } from '../Content/ConfigContent';
|
||||
import { DevToolsLayout } from '../DevToolsLayout';
|
||||
import { InfoContent } from '../Content/InfoContent';
|
||||
import React from 'react';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
|
||||
/** @public */
|
||||
export const DefaultDevToolsPage = () => (
|
||||
<DevToolsLayout>
|
||||
<DevToolsLayout.Route path="info" title="Info">
|
||||
<RequirePermission permission={devToolsInfoReadPermission}>
|
||||
<InfoContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="config" title="Config">
|
||||
<RequirePermission permission={devToolsConfigReadPermission}>
|
||||
<ConfigContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
</DevToolsLayout>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { DefaultDevToolsPage } from './DefaultDevToolsPage';
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 { Header, Page, RoutedTabs } from '@backstage/core-components';
|
||||
import {
|
||||
attachComponentData,
|
||||
useElementFilter,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { TabProps } from '@material-ui/core';
|
||||
import { default as React } from 'react';
|
||||
|
||||
/** @public */
|
||||
export type SubRoute = {
|
||||
path: string;
|
||||
title: string;
|
||||
children: JSX.Element;
|
||||
tabProps?: TabProps<React.ElementType, { component?: React.ElementType }>;
|
||||
};
|
||||
|
||||
const dataKey = 'plugin.devtools.devtoolsLayoutRoute';
|
||||
|
||||
const Route: (props: SubRoute) => null = () => null;
|
||||
attachComponentData(Route, dataKey, true);
|
||||
|
||||
// This causes all mount points that are discovered within this route to use the path of the route itself
|
||||
attachComponentData(Route, 'core.gatherMountPoints', true);
|
||||
|
||||
/** @public */
|
||||
export type DevToolsLayoutProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* DevTools is a compound component, which allows you to define a custom layout
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <DevToolsLayout>
|
||||
* <DevToolsLayout.Route path="/example" title="Example tab">
|
||||
* <div>This is rendered under /example/anything-here route</div>
|
||||
* </DevToolsLayout.Route>
|
||||
* </DevToolsLayout>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export const DevToolsLayout = ({ children }: DevToolsLayoutProps) => {
|
||||
const routes = useElementFilter(children, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: dataKey,
|
||||
withStrictError:
|
||||
'Child of DevToolsLayout must be an DevToolsLayout.Route',
|
||||
})
|
||||
.getElements<SubRoute>()
|
||||
.map(child => child.props),
|
||||
);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header title="Backstage DevTools" />
|
||||
<RoutedTabs routes={routes} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
DevToolsLayout.Route = Route;
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 { DevToolsLayoutProps, SubRoute } from './DevToolsLayout';
|
||||
export { DevToolsLayout } from './DevToolsLayout';
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { useOutlet } from 'react-router-dom';
|
||||
import { DefaultDevToolsPage } from '../DefaultDevToolsPage';
|
||||
|
||||
export const DevToolsPage = () => {
|
||||
const outlet = useOutlet();
|
||||
|
||||
return <>{outlet || <DefaultDevToolsPage />}</>;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { DevToolsPage } from './DevToolsPage';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 * from './Content';
|
||||
export * from './DevToolsLayout';
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { useConfig } from './useConfig';
|
||||
export { useExternalDependencies } from './useExternalDependencies';
|
||||
export { useInfo } from './useInfo';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { devToolsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { ConfigInfo } from '@backstage/plugin-devtools-common';
|
||||
|
||||
export function useConfig(): {
|
||||
configInfo?: ConfigInfo;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const api = useApi(devToolsApiRef);
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getConfig();
|
||||
}, [api]);
|
||||
|
||||
return {
|
||||
configInfo: value,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { devToolsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { ExternalDependency } from '@backstage/plugin-devtools-common';
|
||||
|
||||
export function useExternalDependencies(): {
|
||||
externalDependencies?: ExternalDependency[];
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const api = useApi(devToolsApiRef);
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getExternalDependencies();
|
||||
}, [api]);
|
||||
|
||||
return {
|
||||
externalDependencies: value,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { devToolsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { DevToolsInfo } from '@backstage/plugin-devtools-common';
|
||||
|
||||
export function useInfo(): {
|
||||
about?: DevToolsInfo;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const api = useApi(devToolsApiRef);
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getInfo();
|
||||
}, [api]);
|
||||
|
||||
return {
|
||||
about: value,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 { devToolsPlugin, DevToolsPage } from './plugin';
|
||||
export * from './components';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 { devToolsPlugin } from './plugin';
|
||||
|
||||
describe('devtools', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(devToolsPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 {
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { devToolsApiRef, DevToolsClient } from './api';
|
||||
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
/** @public */
|
||||
export const devToolsPlugin = createPlugin({
|
||||
id: 'devtools',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: devToolsApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new DevToolsClient({ discoveryApi, identityApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const DevToolsPage = devToolsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'DevToolsPage',
|
||||
component: () =>
|
||||
import('./components/DevToolsPage').then(m => m.DevToolsPage),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'devtools',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
@@ -3384,7 +3384,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
version: 7.21.0
|
||||
resolution: "@babel/runtime@npm:7.21.0"
|
||||
dependencies:
|
||||
@@ -6142,6 +6142,86 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-devtools-backend@workspace:^, @backstage/plugin-devtools-backend@workspace:plugins/devtools-backend":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-devtools-backend@workspace:plugins/devtools-backend"
|
||||
dependencies:
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/cli-common": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/config-loader": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/plugin-devtools-common": "workspace:^"
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@manypkg/get-packages": ^1.1.3
|
||||
"@types/express": "*"
|
||||
"@types/minimist": ^1.2.0
|
||||
"@types/ping": ^0.4.1
|
||||
"@types/supertest": ^2.0.8
|
||||
"@types/yarnpkg__lockfile": ^1.1.4
|
||||
"@yarnpkg/lockfile": ^1.1.0
|
||||
"@yarnpkg/parsers": ^3.0.0-rc.4
|
||||
express: ^4.18.1
|
||||
express-promise-router: ^4.1.0
|
||||
fs-extra: ^10.0.0
|
||||
lodash: ^4.17.21
|
||||
msw: ^0.47.0
|
||||
node-fetch: ^2.6.7
|
||||
ping: ^0.4.1
|
||||
semver: ^7.3.2
|
||||
supertest: ^6.2.4
|
||||
winston: ^3.2.1
|
||||
yn: ^4.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-devtools-common@workspace:^, @backstage/plugin-devtools-common@workspace:plugins/devtools-common":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-devtools-common@workspace:plugins/devtools-common"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-devtools@workspace:^, @backstage/plugin-devtools@workspace:plugins/devtools":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-devtools@workspace:plugins/devtools"
|
||||
dependencies:
|
||||
"@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-devtools-common": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@material-ui/core": ^4.9.13
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@material-ui/lab": ^4.0.0-alpha.57
|
||||
"@testing-library/jest-dom": ^5.10.1
|
||||
"@testing-library/react": ^12.1.3
|
||||
"@testing-library/user-event": ^14.0.0
|
||||
"@types/node": "*"
|
||||
cross-fetch: ^3.1.5
|
||||
msw: ^0.47.0
|
||||
react-json-view: ^1.21.3
|
||||
react-use: ^17.2.4
|
||||
peerDependencies:
|
||||
"@types/react": ^16.13.1 || ^17.0.0
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
react-router-dom: 6.0.0-beta.0 || ^6.3.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-dynatrace@workspace:^, @backstage/plugin-dynatrace@workspace:plugins/dynatrace":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-dynatrace@workspace:plugins/dynatrace"
|
||||
@@ -16383,6 +16463,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ping@npm:^0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "@types/ping@npm:0.4.1"
|
||||
checksum: 9b94837fe66df70558c5a42b0e0c8371b4950ab56b96c42c8df809ff2cf52477dd0a7e01d2e6b38af8bb6683b3dcb54587960b96b4b1f3d40fdb529aea348ad0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/pluralize@npm:^0.0.29":
|
||||
version: 0.0.29
|
||||
resolution: "@types/pluralize@npm:0.0.29"
|
||||
@@ -18784,6 +18871,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base16@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "base16@npm:1.0.0"
|
||||
checksum: 0cd449a2db0f0f957e4b6b57e33bc43c9e20d4f1dd744065db94b5da35e8e71fa4dc4bc7a901e59a84d5f8b6936e3c520e2471787f667fc155fb0f50d8540f5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "base64-js@npm:1.5.1"
|
||||
@@ -20882,7 +20976,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-fetch@npm:3.1.5, cross-fetch@npm:^3.0.4, cross-fetch@npm:^3.1.3, cross-fetch@npm:^3.1.5":
|
||||
"cross-fetch@npm:3.1.5, cross-fetch@npm:^3.1.3, cross-fetch@npm:^3.1.5":
|
||||
version: 3.1.5
|
||||
resolution: "cross-fetch@npm:3.1.5"
|
||||
dependencies:
|
||||
@@ -23618,6 +23712,7 @@ __metadata:
|
||||
"@backstage/plugin-cloudbuild": "workspace:^"
|
||||
"@backstage/plugin-code-coverage": "workspace:^"
|
||||
"@backstage/plugin-cost-insights": "workspace:^"
|
||||
"@backstage/plugin-devtools": "workspace:^"
|
||||
"@backstage/plugin-dynatrace": "workspace:^"
|
||||
"@backstage/plugin-entity-feedback": "workspace:^"
|
||||
"@backstage/plugin-explore": "workspace:^"
|
||||
@@ -23741,6 +23836,7 @@ __metadata:
|
||||
"@backstage/plugin-catalog-backend": "workspace:^"
|
||||
"@backstage/plugin-catalog-node": "workspace:^"
|
||||
"@backstage/plugin-code-coverage-backend": "workspace:^"
|
||||
"@backstage/plugin-devtools-backend": "workspace:^"
|
||||
"@backstage/plugin-entity-feedback-backend": "workspace:^"
|
||||
"@backstage/plugin-events-backend": "workspace:^"
|
||||
"@backstage/plugin-events-node": "workspace:^"
|
||||
@@ -24222,6 +24318,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fbemitter@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "fbemitter@npm:3.0.0"
|
||||
dependencies:
|
||||
fbjs: ^3.0.0
|
||||
checksum: 069690b8cdff3521ade3c9beb92ba0a38d818a86ef36dff8690e66749aef58809db4ac0d6938eb1cacea2dbef5f2a508952d455669590264cdc146bbe839f605
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fbjs-css-vars@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "fbjs-css-vars@npm:1.0.2"
|
||||
@@ -24244,18 +24349,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fbjs@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "fbjs@npm:3.0.0"
|
||||
"fbjs@npm:^3.0.0, fbjs@npm:^3.0.1":
|
||||
version: 3.0.4
|
||||
resolution: "fbjs@npm:3.0.4"
|
||||
dependencies:
|
||||
cross-fetch: ^3.0.4
|
||||
cross-fetch: ^3.1.5
|
||||
fbjs-css-vars: ^1.0.0
|
||||
loose-envify: ^1.0.0
|
||||
object-assign: ^4.1.0
|
||||
promise: ^7.1.1
|
||||
setimmediate: ^1.0.5
|
||||
ua-parser-js: ^0.7.18
|
||||
checksum: 85ec57d8dbeddd7c82bf8f111a3c7de1abc1f4d7c603d6ccbcc1ec8dce35ff5b7a113dd34acbf7930093e5533c37a2298a92d342077f967bef34dc7cf2f3f07e
|
||||
ua-parser-js: ^0.7.30
|
||||
checksum: 8b23a3550fcda8a9109fca9475a3416590c18bb6825ea884192864ed686f67fcd618e308a140c9e5444fbd0168732e1ff3c092ba3d0c0ae1768969f32ba280c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -24499,6 +24604,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"flux@npm:^4.0.1":
|
||||
version: 4.0.4
|
||||
resolution: "flux@npm:4.0.4"
|
||||
dependencies:
|
||||
fbemitter: ^3.0.0
|
||||
fbjs: ^3.0.1
|
||||
peerDependencies:
|
||||
react: ^15.0.2 || ^16.0.0 || ^17.0.0
|
||||
checksum: 8fa5c2f9322258de3e331f67c6f1078a7f91c4dec9dbe8a54c4b8a80eed19a4f91889028b768668af4a796e8f2ee75e461e1571b8615432a3920ae95cc4ff794
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fn.name@npm:1.x.x":
|
||||
version: 1.1.0
|
||||
resolution: "fn.name@npm:1.1.0"
|
||||
@@ -29439,6 +29556,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.curry@npm:^4.0.1":
|
||||
version: 4.1.1
|
||||
resolution: "lodash.curry@npm:4.1.1"
|
||||
checksum: 9192b70fe7df4d1ff780c0260bee271afa9168c93fe4fa24bc861900240531b59781b5fdaadf4644fea8f4fbcd96f0700539ab294b579ffc1022c6c15dcc462a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.debounce@npm:^4, lodash.debounce@npm:^4.0.8":
|
||||
version: 4.0.8
|
||||
resolution: "lodash.debounce@npm:4.0.8"
|
||||
@@ -29474,6 +29598,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.flow@npm:^3.3.0":
|
||||
version: 3.5.0
|
||||
resolution: "lodash.flow@npm:3.5.0"
|
||||
checksum: a9a62ad344e3c5a1f42bc121da20f64dd855aaafecee24b1db640f29b88bd165d81c37ff7e380a7191de6f70b26f5918abcebbee8396624f78f3618a0b18634c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.get@npm:^4.4.2":
|
||||
version: 4.4.2
|
||||
resolution: "lodash.get@npm:4.4.2"
|
||||
@@ -31083,6 +31214,41 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msw@npm:^0.47.0":
|
||||
version: 0.47.4
|
||||
resolution: "msw@npm:0.47.4"
|
||||
dependencies:
|
||||
"@mswjs/cookies": ^0.2.2
|
||||
"@mswjs/interceptors": ^0.17.5
|
||||
"@open-draft/until": ^1.0.3
|
||||
"@types/cookie": ^0.4.1
|
||||
"@types/js-levenshtein": ^1.1.1
|
||||
chalk: 4.1.1
|
||||
chokidar: ^3.4.2
|
||||
cookie: ^0.4.2
|
||||
graphql: ^15.0.0 || ^16.0.0
|
||||
headers-polyfill: ^3.1.0
|
||||
inquirer: ^8.2.0
|
||||
is-node-process: ^1.0.1
|
||||
js-levenshtein: ^1.1.6
|
||||
node-fetch: ^2.6.7
|
||||
outvariant: ^1.3.0
|
||||
path-to-regexp: ^6.2.0
|
||||
statuses: ^2.0.0
|
||||
strict-event-emitter: ^0.2.6
|
||||
type-fest: ^2.19.0
|
||||
yargs: ^17.3.1
|
||||
peerDependencies:
|
||||
typescript: ">= 4.2.x <= 4.8.x"
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
bin:
|
||||
msw: cli/index.js
|
||||
checksum: 10ff632641d40384d6622abf4df6399e4ae649db0f676b5d1ee2d0a515ec96f33abe9d4fecba08cdba4b2e43255af419da9eefc020d40a7e10669d0906457197
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msw@npm:^0.49.0":
|
||||
version: 0.49.3
|
||||
resolution: "msw@npm:0.49.3"
|
||||
@@ -33154,6 +33320,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ping@npm:^0.4.1":
|
||||
version: 0.4.4
|
||||
resolution: "ping@npm:0.4.4"
|
||||
checksum: cab10af309312e3a6822eccb7f323f0c9a1e17ef5895954114e31b6a1cced5e2ced3563990b79c66d074372e75aa4c846d9001120ee296b365de6a2adef5b8f9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pinkie-promise@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "pinkie-promise@npm:2.0.1"
|
||||
@@ -34242,6 +34415,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pure-color@npm:^1.2.0":
|
||||
version: 1.3.0
|
||||
resolution: "pure-color@npm:1.3.0"
|
||||
checksum: 646d8bed6e6eab89affdd5e2c11f607a85b631a7fb03c061dfa658eb4dc4806881a15feed2ac5fd8c0bad8c00c632c640d5b1cb8b9a972e6e947393a1329371b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pvtsutils@npm:^1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "pvtsutils@npm:1.3.2"
|
||||
@@ -34474,6 +34654,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-base16-styling@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "react-base16-styling@npm:0.6.0"
|
||||
dependencies:
|
||||
base16: ^1.0.0
|
||||
lodash.curry: ^4.0.1
|
||||
lodash.flow: ^3.3.0
|
||||
pure-color: ^1.2.0
|
||||
checksum: 00a12dddafc8a9025cca933b0dcb65fca41c81fa176d1fc3a6a9d0242127042e2c0a604f4c724a3254dd2c6aeb5ef55095522ff22f5462e419641c1341a658e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-beautiful-dnd@npm:^13.0.0":
|
||||
version: 13.0.0
|
||||
resolution: "react-beautiful-dnd@npm:13.0.0"
|
||||
@@ -34743,6 +34935,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-json-view@npm:^1.21.3":
|
||||
version: 1.21.3
|
||||
resolution: "react-json-view@npm:1.21.3"
|
||||
dependencies:
|
||||
flux: ^4.0.1
|
||||
react-base16-styling: ^0.6.0
|
||||
react-lifecycles-compat: ^3.0.4
|
||||
react-textarea-autosize: ^8.3.2
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^16.3.0 || ^15.5.4
|
||||
react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4
|
||||
checksum: 5718bcd9210ad5b06eb9469cf8b9b44be9498845a7702e621343618e8251f26357e6e1c865532cf170db6165df1cb30202787e057309d8848c220bc600ec0d1a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-lifecycles-compat@npm:^3.0.2, react-lifecycles-compat@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "react-lifecycles-compat@npm:3.0.4"
|
||||
@@ -35010,6 +35217,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-textarea-autosize@npm:^8.3.2":
|
||||
version: 8.4.1
|
||||
resolution: "react-textarea-autosize@npm:8.4.1"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.20.13
|
||||
use-composed-ref: ^1.3.0
|
||||
use-latest: ^1.2.1
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: b200437cd68938c23b13944fe6fdfeb32a6d949ac88588307f14d6fcdaba3044b8c7d8e239851b081f2101d433b93d4cf5aa027543b170b84f2a0cbe6fc9093f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-transition-group@npm:2.9.0, react-transition-group@npm:^2.2.1":
|
||||
version: 2.9.0
|
||||
resolution: "react-transition-group@npm:2.9.0"
|
||||
@@ -37374,7 +37594,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"statuses@npm:2.0.1":
|
||||
"statuses@npm:2.0.1, statuses@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "statuses@npm:2.0.1"
|
||||
checksum: 18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb
|
||||
@@ -37476,12 +37696,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strict-event-emitter@npm:^0.2.4":
|
||||
version: 0.2.7
|
||||
resolution: "strict-event-emitter@npm:0.2.7"
|
||||
"strict-event-emitter@npm:^0.2.4, strict-event-emitter@npm:^0.2.6":
|
||||
version: 0.2.8
|
||||
resolution: "strict-event-emitter@npm:0.2.8"
|
||||
dependencies:
|
||||
events: ^3.3.0
|
||||
checksum: 111691e7d3fce0810586ccd8e8234af883ad3b121ef69091c7e260c32299d1ba085a95238ad09b43478bc5e9e80370f2fcb8114716e343be6f44bfc08fab4142
|
||||
checksum: 6ac06fe72a6ee6ae64d20f1dd42838ea67342f1b5f32b03b3050d73ee6ecee44b4d5c4ed2965a7154b47991e215f373d4e789e2b2be2769cd80e356126c2ca53
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -39046,7 +39266,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ua-parser-js@npm:^0.7.18, ua-parser-js@npm:^0.7.30":
|
||||
"ua-parser-js@npm:^0.7.30":
|
||||
version: 0.7.33
|
||||
resolution: "ua-parser-js@npm:0.7.33"
|
||||
checksum: 1510e9ec26fcaf0d8c6ae8f1078a8230e8816f083e1b5f453ea19d06b8ef2b8a596601c92148fd41899e8b3e5f83fa69c42332bd5729b931a721040339831696
|
||||
@@ -39446,6 +39666,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-composed-ref@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "use-composed-ref@npm:1.3.0"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: f771cbadfdc91e03b7ab9eb32d0fc0cc647755711801bf507e891ad38c4bbc5f02b2509acadf9c965ec9c5f2f642fd33bdfdfb17b0873c4ad0a9b1f5e5e724bf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-deep-compare-effect@npm:^1.8.1":
|
||||
version: 1.8.1
|
||||
resolution: "use-deep-compare-effect@npm:1.8.1"
|
||||
@@ -39468,6 +39697,32 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-isomorphic-layout-effect@npm:^1.1.1":
|
||||
version: 1.1.2
|
||||
resolution: "use-isomorphic-layout-effect@npm:1.1.2"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: a6532f7fc9ae222c3725ff0308aaf1f1ddbd3c00d685ef9eee6714fd0684de5cb9741b432fbf51e61a784e2955424864f7ea9f99734a02f237b17ad3e18ea5cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-latest@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "use-latest@npm:1.2.1"
|
||||
dependencies:
|
||||
use-isomorphic-layout-effect: ^1.1.1
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: ed3f2ddddf6f21825e2ede4c2e0f0db8dcce5129802b69d1f0575fc1b42380436e8c76a6cd885d4e9aa8e292e60fb8b959c955f33c6a9123b83814a1a1875367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-memo-one@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "use-memo-one@npm:1.1.1"
|
||||
|
||||
Reference in New Issue
Block a user