Merge pull request #13634 from wesley-pattison/plugin-azure-functions
Plugin azure functions
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-azure-sites': minor
|
||||
'@backstage/plugin-azure-sites-backend': minor
|
||||
'@backstage/plugin-azure-sites-common': minor
|
||||
---
|
||||
|
||||
Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Azure Sites
|
||||
author: FRISS
|
||||
authorUrl: https://friss.com
|
||||
category: Infrastructure
|
||||
description: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/azure-sites
|
||||
npmPackageName: '@backstage/plugin-azure-sites'
|
||||
@@ -19,6 +19,7 @@
|
||||
"@backstage/plugin-apache-airflow": "workspace:^",
|
||||
"@backstage/plugin-api-docs": "workspace:^",
|
||||
"@backstage/plugin-azure-devops": "workspace:^",
|
||||
"@backstage/plugin-azure-sites": "workspace:^",
|
||||
"@backstage/plugin-badges": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-catalog-graph": "workspace:^",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@backstage/plugin-auth-backend": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/plugin-azure-devops-backend": "workspace:^",
|
||||
"@backstage/plugin-azure-sites-backend": "workspace:^",
|
||||
"@backstage/plugin-badges-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-code-coverage-backend": "workspace:^",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,85 @@
|
||||
# Azure Sites Backend
|
||||
|
||||
Simple plugin that proxies requests to the Azure Portal API through Azure SDK JavaScript libraries.
|
||||
|
||||
_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_
|
||||
|
||||
## Setup
|
||||
|
||||
The following sections will help you get the Azure Sites Backend plugin setup and running.
|
||||
|
||||
### Configuration
|
||||
|
||||
The Azure plugin requires the following YAML to be added to your app-config.yaml:
|
||||
|
||||
```yaml
|
||||
azureSites:
|
||||
domain:
|
||||
tenantId:
|
||||
clientId:
|
||||
clientSecret:
|
||||
allowedSubscriptions:
|
||||
- id:
|
||||
```
|
||||
|
||||
Configuration Details:
|
||||
|
||||
- `domain` can be found by visiting the [Directories + Subscriptions settings page](https://portal.azure.com/#settings/directory). Alternatively you can inspect the [Azure home](https://portal.azure.com/#home) URL - `https://portal.azure.com/#@<Your_Domain>/`.
|
||||
- `tenantId` can be found by visiting [Azure Directory Overview page](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade).
|
||||
- (Optional) `clientId` and `clientSecret` can be the same values you used for [Azure DevOps Backend](https://github.com/backstage/backstage/tree/master/plugins/azure-devops-backend) or [Azure Integration](https://backstage.io/docs/integrations/azure/org#app-registration) as long as this App Registration has permissions to read your function apps.
|
||||
- (Optional) `allowedSubscriptions` is an array of `id` that will be used to iterate over and look for the specified functions' app. `id` can be found the [Subscriptions page](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade).
|
||||
|
||||
### Integrating
|
||||
|
||||
Here's how to get the backend plugin up and running:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-azure-sites-backend` package to your backend:
|
||||
|
||||
```sh
|
||||
# From the Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-azure-sites-backend
|
||||
```
|
||||
|
||||
2. Then we will create a new file named `packages/backend/src/plugins/azure.ts`, and add the following to it:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createRouter,
|
||||
AzureSitesApi,
|
||||
} from '@backstage/plugin-azure-sites-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
azureSitesApi: AzureSitesApi.fromConfig(env.config),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
import azure from './plugins/azure';
|
||||
|
||||
// Removed for clairty...
|
||||
|
||||
async function main() {
|
||||
// ...
|
||||
// Add this line under the other lines that follow the useHotMemoize pattern
|
||||
const azureSitesEnv = useHotMemoize(module, () =>
|
||||
createEnv('azure-sites'),
|
||||
);
|
||||
|
||||
// ...
|
||||
// Insert this line under the other lines that add their routers to apiRouter in the same way
|
||||
apiRouter.use('/azure-sites', await azure(azureSitesEnv));
|
||||
}
|
||||
```
|
||||
|
||||
4. Now run `yarn start-backend` from the repo root.
|
||||
|
||||
5. Finally, open `http://localhost:7007/api/azure/health` in a browser, it should return `{"status":"ok"}`.
|
||||
@@ -0,0 +1,61 @@
|
||||
## API Report File for "@backstage/plugin-azure-sites-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common';
|
||||
import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common';
|
||||
import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureSitesApi {
|
||||
constructor(config: AzureSitesConfig);
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config): AzureSitesApi;
|
||||
// (undocumented)
|
||||
list(request: AzureSiteListRequest): Promise<AzureSiteListResponse>;
|
||||
// (undocumented)
|
||||
start(request: AzureSiteStartStopRequest): Promise<void>;
|
||||
// (undocumented)
|
||||
stop(request: AzureSiteStartStopRequest): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureSitesConfig {
|
||||
constructor(
|
||||
domain: string,
|
||||
tenantId: string,
|
||||
subscriptions?: string[] | undefined,
|
||||
clientId?: string | undefined,
|
||||
clientSecret?: string | undefined,
|
||||
);
|
||||
// (undocumented)
|
||||
readonly clientId?: string | undefined;
|
||||
// (undocumented)
|
||||
readonly clientSecret?: string | undefined;
|
||||
// (undocumented)
|
||||
readonly domain: string;
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config): AzureSitesConfig;
|
||||
// (undocumented)
|
||||
readonly subscriptions?: string[] | undefined;
|
||||
// (undocumented)
|
||||
readonly tenantId: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
azureSitesApi: AzureSitesApi;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-sites-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"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/azure-sites-backend"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"azure"
|
||||
],
|
||||
"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": {
|
||||
"@azure/arm-appservice": "^13.0.1",
|
||||
"@azure/arm-resourcegraph": "^4.2.1",
|
||||
"@azure/identity": "^2.1.0",
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-azure-sites-common": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.47.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 {
|
||||
azureSites: {
|
||||
/** @visibility backend */
|
||||
domain: string;
|
||||
/** @visibility backend */
|
||||
tenantId: string;
|
||||
/** @visibility backend */
|
||||
subscriptions?: [
|
||||
{
|
||||
id: string;
|
||||
},
|
||||
];
|
||||
/** @visibility backend */
|
||||
clientId?: string;
|
||||
/** @visibility secret */
|
||||
clientSecret?: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 } from '@backstage/config';
|
||||
import {
|
||||
DefaultAzureCredential,
|
||||
ClientSecretCredential,
|
||||
} from '@azure/identity';
|
||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||
import { WebSiteManagementClient } from '@azure/arm-appservice';
|
||||
import {
|
||||
AzureSite,
|
||||
AzureSiteListRequest,
|
||||
AzureSiteListResponse,
|
||||
AzureSiteStartStopRequest,
|
||||
} from '@backstage/plugin-azure-sites-common';
|
||||
import { AzureSitesConfig } from '../config';
|
||||
|
||||
/** @public */
|
||||
export class AzureSitesApi {
|
||||
private readonly baseHref = (domain: string) =>
|
||||
`https://portal.azure.com/#@${domain}/resource`;
|
||||
private readonly client: ResourceGraphClient;
|
||||
|
||||
constructor(private readonly config: AzureSitesConfig) {
|
||||
const creds = this.getCredentials(config);
|
||||
|
||||
this.client = new ResourceGraphClient(creds);
|
||||
}
|
||||
|
||||
private getCredentials(config: AzureSitesConfig) {
|
||||
return config.clientId && config.clientSecret
|
||||
? new ClientSecretCredential(
|
||||
config.tenantId,
|
||||
config.clientId,
|
||||
config.clientSecret,
|
||||
)
|
||||
: new DefaultAzureCredential({ tenantId: config.tenantId });
|
||||
}
|
||||
|
||||
static fromConfig(config: Config): AzureSitesApi {
|
||||
return new AzureSitesApi(AzureSitesConfig.fromConfig(config));
|
||||
}
|
||||
|
||||
async start(request: AzureSiteStartStopRequest): Promise<void> {
|
||||
const client = new WebSiteManagementClient(
|
||||
this.getCredentials(this.config),
|
||||
request.subscription,
|
||||
);
|
||||
await client.webApps.start(request.resourceGroup, request.name);
|
||||
}
|
||||
|
||||
async stop(request: AzureSiteStartStopRequest): Promise<void> {
|
||||
const client = new WebSiteManagementClient(
|
||||
this.getCredentials(this.config),
|
||||
request.subscription,
|
||||
);
|
||||
await client.webApps.stop(request.resourceGroup, request.name);
|
||||
}
|
||||
|
||||
async list(request: AzureSiteListRequest): Promise<AzureSiteListResponse> {
|
||||
const items: AzureSite[] = [];
|
||||
const result = await this.client.resources({
|
||||
query: `resources | where type == 'microsoft.web/sites' | where name contains '${request.name}'`,
|
||||
subscriptions: this.config.subscriptions,
|
||||
});
|
||||
for (const v of result.data) {
|
||||
items.push({
|
||||
href: `${this.baseHref(this.config.domain)}${v.id!}`,
|
||||
logstreamHref: `${this.baseHref(this.config.domain)}${v.id!}/logStream`,
|
||||
name: v.name!,
|
||||
kind: v.kind!,
|
||||
resourceGroup: v.resourceGroup!,
|
||||
subscription: v.id!.match(/\w{8}\-\w{4}\-\w{4}\-\w{4}\-\w{12}/)[0],
|
||||
location: v.location!,
|
||||
lastModifiedDate: v.properties.lastModifiedTimeUtc!,
|
||||
usageState: v.properties.usageState!,
|
||||
state: v.properties.state!,
|
||||
containerSize: v.properties.containerSize!,
|
||||
tags: v.properties.tags!,
|
||||
});
|
||||
}
|
||||
return { items: items };
|
||||
}
|
||||
}
|
||||
@@ -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 { AzureSitesApi } from './AzureSitesApi';
|
||||
@@ -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 { Config } from '@backstage/config';
|
||||
|
||||
/** @public */
|
||||
export class AzureSitesConfig {
|
||||
constructor(
|
||||
public readonly domain: string,
|
||||
public readonly tenantId: string,
|
||||
public readonly subscriptions?: string[],
|
||||
public readonly clientId?: string,
|
||||
public readonly clientSecret?: string,
|
||||
) {}
|
||||
|
||||
static fromConfig(config: Config): AzureSitesConfig {
|
||||
const azConfig = config.getConfig('azureSites');
|
||||
|
||||
return new AzureSitesConfig(
|
||||
azConfig.getString('domain'),
|
||||
azConfig.getString('tenantId'),
|
||||
azConfig
|
||||
.getOptionalConfigArray('subscriptions')
|
||||
?.map<string>(as => as.getString('id')),
|
||||
azConfig.getOptionalString('clientId'),
|
||||
azConfig.getOptionalString('clientSecret'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 './service/router';
|
||||
export * from './api';
|
||||
export * from './config';
|
||||
@@ -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,81 @@
|
||||
/*
|
||||
* 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 { errorHandler } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
import { AzureSitesApi } from '../api';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
azureSitesApi: AzureSitesApi;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger, azureSitesApi } = options;
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/health', (_, response) => {
|
||||
logger.info('PONG!');
|
||||
response.send({ status: 'ok' });
|
||||
});
|
||||
router.get('/list/:name', async (request, response) => {
|
||||
const { name } = request.params;
|
||||
response.json(
|
||||
await azureSitesApi.list({
|
||||
name: name,
|
||||
}),
|
||||
);
|
||||
});
|
||||
router.post(
|
||||
'/:subscription/:resourceGroup/:name/start',
|
||||
async (request, response) => {
|
||||
const { subscription, resourceGroup, name } = request.params;
|
||||
console.log('starting...');
|
||||
response.json(
|
||||
await azureSitesApi.start({
|
||||
subscription,
|
||||
resourceGroup,
|
||||
name,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
router.post(
|
||||
'/:subscription/:resourceGroup/:name/stop',
|
||||
async (request, response) => {
|
||||
const { subscription, resourceGroup, name } = request.params;
|
||||
console.log('stopping...');
|
||||
response.json(
|
||||
await azureSitesApi.stop({
|
||||
subscription,
|
||||
resourceGroup,
|
||||
name,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { AzureSitesApi } from '../api';
|
||||
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: 'azure-backend' });
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
logger.debug('Starting application server...');
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
azureSitesApi: AzureSitesApi.fromConfig(config),
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/azure-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 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,40 @@
|
||||
## API Report File for "@backstage/plugin-azure-sites-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
// @public (undocumented)
|
||||
export type AzureSite = {
|
||||
href: string;
|
||||
logstreamHref: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
location: string;
|
||||
resourceGroup: string;
|
||||
subscription: string;
|
||||
state: string;
|
||||
usageState: string;
|
||||
containerSize: number;
|
||||
lastModifiedDate: string;
|
||||
tags: {};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AzureSiteListRequest = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AzureSiteListResponse = {
|
||||
items: AzureSite[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AzureSiteStartStopRequest = {
|
||||
subscription: string;
|
||||
resourceGroup: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-sites-common",
|
||||
"description": "Common functionalities for the azure 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"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/azure-sites-common"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -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 * from './types';
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export type AzureSite = {
|
||||
href: string;
|
||||
logstreamHref: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
location: string;
|
||||
resourceGroup: string;
|
||||
subscription: string;
|
||||
state: string;
|
||||
usageState: string;
|
||||
containerSize: number;
|
||||
lastModifiedDate: string;
|
||||
tags: {};
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type AzureSiteListRequest = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type AzureSiteListResponse = {
|
||||
items: AzureSite[];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type AzureSiteStartStopRequest = {
|
||||
subscription: string;
|
||||
resourceGroup: string;
|
||||
name: string;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,70 @@
|
||||
# Azure Sites Plugin
|
||||
|
||||

|
||||
|
||||
_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_
|
||||
|
||||
## Features
|
||||
|
||||
- Azure overview table
|
||||
|
||||
## Plugin Setup
|
||||
|
||||
The following sections will help you get the Azure plugin setup and running
|
||||
|
||||
### Azure Sites Backend
|
||||
|
||||
You need to set up the Azure Sites Backend plugin before you move forward with any of these steps if you haven't already.
|
||||
|
||||
### Entity Annotation
|
||||
|
||||
To be able to use the Azure Sites plugin you need to add the following annotation to any entities you want to use it with:
|
||||
|
||||
```yaml
|
||||
azure.com/microsoft-web-sites: <name>
|
||||
```
|
||||
|
||||
`<name>` supports case-insensitive exact / partial value.
|
||||
|
||||
Example of Partial Matching:
|
||||
|
||||
Let's say you have a number of functions apps, spread out over different regions (and possibly different subscriptions), and they follow a naming convention:
|
||||
|
||||
```
|
||||
func-testapp-eu
|
||||
func-testapp-ca
|
||||
func-testapp-us
|
||||
```
|
||||
|
||||
The annotation you will use to have the three functions' app appear in the overview table would look like this:
|
||||
|
||||
```yaml
|
||||
azure.com/microsoft-web-sites: func-testapp
|
||||
```
|
||||
|
||||
### Install the component
|
||||
|
||||
1. Install the plugin in the `packages/app` directory
|
||||
|
||||
```sh
|
||||
yarn add @backstage/plugin-azure-sites
|
||||
```
|
||||
|
||||
2. Add widget component to your Backstage instance:
|
||||
|
||||
```ts
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import { EntityAzureSitesOverviewWidget, isAzureWebSiteNameAvailable } from '@backstage/plugin-azure-sites';
|
||||
|
||||
...
|
||||
|
||||
const serviceEntityPage = (
|
||||
<EntityLayout>
|
||||
//...
|
||||
<EntityLayout.Route if={e => Boolean(isAzureWebSiteNameAvailable(e))} path="/azure" title="Azure">
|
||||
<EntityAzureSitesOverviewWidget />
|
||||
</EntityLayout.Route>
|
||||
//...
|
||||
</EntityLayout>
|
||||
);
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
## API Report File for "@backstage/plugin-azure-sites"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common';
|
||||
import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common';
|
||||
import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export const azureSiteApiRef: ApiRef<AzureSitesApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type AzureSitesApi = {
|
||||
list: (request: AzureSiteListRequest) => Promise<AzureSiteListResponse>;
|
||||
start: (request: AzureSiteStartStopRequest) => Promise<void>;
|
||||
stop: (request: AzureSiteStartStopRequest) => Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureSitesApiBackendClient implements AzureSitesApi {
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
});
|
||||
// (undocumented)
|
||||
list(request: AzureSiteListRequest): Promise<AzureSiteListResponse>;
|
||||
// (undocumented)
|
||||
start(request: AzureSiteStartStopRequest): Promise<void>;
|
||||
// (undocumented)
|
||||
stop(request: AzureSiteStartStopRequest): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const AzureSitesOverviewWidget: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const azureSitesPlugin: BackstagePlugin<
|
||||
{
|
||||
entityContent: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityAzureSitesOverviewWidget: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isAzureWebSiteNameAvailable: (
|
||||
entity: Entity,
|
||||
) => string | undefined;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { azureSitesPlugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(azureSitesPlugin).render();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-sites",
|
||||
"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/azure-sites"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"azure"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-azure-sites-common": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"luxon": "^3.0.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.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": "^16.11.26",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.47.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 {
|
||||
AzureSiteListRequest,
|
||||
AzureSiteListResponse,
|
||||
AzureSiteStartStopRequest,
|
||||
} from '@backstage/plugin-azure-sites-common';
|
||||
|
||||
/** @public */
|
||||
export const azureSiteApiRef = createApiRef<AzureSitesApi>({
|
||||
id: 'plugin.azure-sites.service',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export type AzureSitesApi = {
|
||||
list: (request: AzureSiteListRequest) => Promise<AzureSiteListResponse>;
|
||||
start: (request: AzureSiteStartStopRequest) => Promise<void>;
|
||||
stop: (request: AzureSiteStartStopRequest) => Promise<void>;
|
||||
};
|
||||
@@ -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 { AzureSitesApi } from './AzureSitesApi';
|
||||
import {
|
||||
AzureSiteListRequest,
|
||||
AzureSiteListResponse,
|
||||
AzureSiteStartStopRequest,
|
||||
} from '@backstage/plugin-azure-sites-common';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export class AzureSitesApiBackendClient implements AzureSitesApi {
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
}
|
||||
|
||||
async stop(request: AzureSiteStartStopRequest): Promise<void> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${
|
||||
request.subscription
|
||||
}/${request.resourceGroup}/${request.name}/stop`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
||||
},
|
||||
});
|
||||
}
|
||||
async start(request: AzureSiteStartStopRequest): Promise<void> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${
|
||||
request.subscription
|
||||
}/${request.resourceGroup}/${request.name}/start`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async list(request: AzureSiteListRequest): Promise<AzureSiteListResponse> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure-sites')}/list/${
|
||||
request.name
|
||||
}`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
||||
},
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
@@ -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 './AzureSitesApi';
|
||||
export * from './AzureSitesApiBackendClient';
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { useSites } from '../../hooks/useSites';
|
||||
import {
|
||||
AZURE_WEB_SITE_NAME_ANNOTATION,
|
||||
useServiceEntityAnnotations,
|
||||
} from '../../hooks/useServiceEntityAnnotations';
|
||||
import {
|
||||
ErrorBoundary,
|
||||
MissingAnnotationEmptyState,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { AzureSitesOverviewTable } from '../AzureSitesOverviewTableComponent/AzureSitesOverviewTable';
|
||||
|
||||
/** @public */
|
||||
export const isAzureWebSiteNameAvailable = (entity: Entity) =>
|
||||
entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION];
|
||||
|
||||
const AzureSitesOverview = ({ entity }: { entity: Entity }) => {
|
||||
const { webSiteName } = useServiceEntityAnnotations(entity);
|
||||
|
||||
const [sites] = useSites({
|
||||
name: webSiteName,
|
||||
});
|
||||
|
||||
if (sites.error) {
|
||||
return (
|
||||
<div>
|
||||
<ResponseErrorPanel error={sites.error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AzureSitesOverviewTable
|
||||
data={sites.data?.items ?? []}
|
||||
loading={sites.loading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const AzureSitesOverviewWidget = () => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
if (!isAzureWebSiteNameAvailable(entity)) {
|
||||
return (
|
||||
<MissingAnnotationEmptyState
|
||||
annotation={AZURE_WEB_SITE_NAME_ANNOTATION}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<AzureSitesOverview entity={entity} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import {
|
||||
AnyApiRef,
|
||||
configApiRef,
|
||||
errorApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { rest } from 'msw';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { DateTime } from 'luxon';
|
||||
import { siteMock } from '../../mocks/mocks';
|
||||
import { azureSiteApiRef } from '../..';
|
||||
import { AzureSitesOverviewTable } from './AzureSitesOverviewTable';
|
||||
|
||||
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
|
||||
const identityApiMock = (getCredentials: any) => ({
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials,
|
||||
});
|
||||
const azureSitesApiMock = {};
|
||||
|
||||
const config = {
|
||||
getString: (_: string) => 'https://test-url',
|
||||
};
|
||||
|
||||
const apis: [AnyApiRef, Partial<unknown>][] = [
|
||||
[errorApiRef, errorApiMock],
|
||||
[configApiRef, config],
|
||||
[azureSiteApiRef, azureSitesApiMock],
|
||||
[identityApiRef, identityApiMock],
|
||||
];
|
||||
|
||||
describe('AzureSitesOverviewWidget', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get('/list/func-mock', (_, res, ctx) => {
|
||||
res(ctx.json(siteMock));
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should display an overview table with the data from the requests', async () => {
|
||||
const rendered = render(
|
||||
<TestApiProvider apis={apis}>
|
||||
<AzureSitesOverviewTable data={[siteMock]} loading={false} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(await rendered.findByText(siteMock.name)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.location)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.state)).toBeInTheDocument();
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
),
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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, { Dispatch, useEffect, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
Link,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Snackbar,
|
||||
Tooltip,
|
||||
} from '@material-ui/core';
|
||||
import { default as MuiAlert } from '@material-ui/lab/Alert';
|
||||
import { AzureSite } from '@backstage/plugin-azure-sites-common';
|
||||
import { Table, TableColumn } from '@backstage/core-components';
|
||||
import FlashOnIcon from '@material-ui/icons/FlashOn';
|
||||
import PublicIcon from '@material-ui/icons/Public';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import StartIcon from '@material-ui/icons/PlayArrow';
|
||||
import StopIcon from '@material-ui/icons/Stop';
|
||||
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { azureSiteApiRef } from '../../api';
|
||||
|
||||
type States = 'Waiting' | 'Running' | 'Paused' | 'Failed' | 'Stopped';
|
||||
type Kinds = 'app' | 'functionapp';
|
||||
|
||||
const State = ({ value }: { value: States }) => {
|
||||
const colorMap = {
|
||||
Waiting: '#dcbc21',
|
||||
Running: 'green',
|
||||
Paused: 'black',
|
||||
Failed: 'red',
|
||||
Stopped: 'black',
|
||||
};
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<span
|
||||
style={{
|
||||
display: 'block',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: colorMap[value],
|
||||
marginRight: '5px',
|
||||
}}
|
||||
/>
|
||||
{value}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Kind = ({ value }: { value: Kinds }) => {
|
||||
const iconMap = {
|
||||
app: <PublicIcon />,
|
||||
functionapp: <FlashOnIcon />,
|
||||
};
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Tooltip title={value}>{iconMap[value]}</Tooltip>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
type TableProps = {
|
||||
data: AzureSite[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
const ActionButtons = ({
|
||||
value,
|
||||
onMenuItemClick,
|
||||
}: {
|
||||
value: AzureSite;
|
||||
onMenuItemClick: Dispatch<React.SetStateAction<string | null>>;
|
||||
}) => {
|
||||
const azureApi = useApi(azureSiteApiRef);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const start = () => {
|
||||
azureApi.start({
|
||||
name: value.name,
|
||||
resourceGroup: value.resourceGroup,
|
||||
subscription: value.subscription,
|
||||
});
|
||||
onMenuItemClick('Starting, this may take some time...');
|
||||
handleClose();
|
||||
};
|
||||
const stop = () => {
|
||||
azureApi.stop({
|
||||
name: value.name,
|
||||
resourceGroup: value.resourceGroup,
|
||||
subscription: value.subscription,
|
||||
});
|
||||
onMenuItemClick('Stopping, this may take some time...');
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
id="long-button"
|
||||
aria-controls={open ? 'long-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="long-menu"
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'long-button',
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxHeight: 48 * 4.5,
|
||||
width: '20ch',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{value.state !== 'Running' && (
|
||||
<MenuItem key="start" onClick={start}>
|
||||
<StartIcon />
|
||||
Start
|
||||
</MenuItem>
|
||||
)}
|
||||
{value.state !== 'Stopped' && (
|
||||
<MenuItem key="stop" onClick={stop}>
|
||||
<StopIcon />
|
||||
Stop
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
component="a"
|
||||
href={value.logstreamHref}
|
||||
target="_blank"
|
||||
key="logStream"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<OpenInNewIcon />
|
||||
Log Stream
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const AzureSitesOverviewTable = ({ data, loading }: TableProps) => {
|
||||
const [snackbarMessage, setSnackbarMessage] = useState<null | string>(null);
|
||||
const [isSnackbarOpen, setSnackbarOpen] = useState(false);
|
||||
|
||||
const onSnackbarClose = () => {
|
||||
setSnackbarMessage(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSnackbarOpen(!!snackbarMessage);
|
||||
}, [snackbarMessage]);
|
||||
|
||||
const columns: TableColumn<AzureSite>[] = [
|
||||
{
|
||||
width: '25px',
|
||||
field: 'kind',
|
||||
render: (func: AzureSite) => <Kind value={func.kind as Kinds} />,
|
||||
},
|
||||
{
|
||||
title: 'name',
|
||||
field: 'name',
|
||||
highlight: true,
|
||||
render: (func: AzureSite) => {
|
||||
return (
|
||||
<Link href={func.href} target="_blank">
|
||||
{func.name}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'location',
|
||||
field: 'location',
|
||||
render: (func: AzureSite) => func.location ?? 'unknown',
|
||||
},
|
||||
{
|
||||
title: 'status',
|
||||
field: 'status',
|
||||
render: (func: AzureSite) => <State value={func.state as States} />,
|
||||
},
|
||||
{
|
||||
title: 'last modified',
|
||||
field: 'lastModifiedDate',
|
||||
render: (func: AzureSite) =>
|
||||
DateTime.fromISO(func.lastModifiedDate).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'actions',
|
||||
align: 'right',
|
||||
sorting: false,
|
||||
field: 'actions',
|
||||
render: (func: AzureSite) => (
|
||||
<ActionButtons value={func} onMenuItemClick={setSnackbarMessage} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const tableStyle = {
|
||||
minWidth: '0',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
<Card style={tableStyle}>
|
||||
<Table
|
||||
title={
|
||||
<Box display="flex" alignItems="center">
|
||||
<FlashOnIcon style={{ fontSize: 30 }} />
|
||||
<Box mr={1} />
|
||||
Azure Sites
|
||||
</Box>
|
||||
}
|
||||
options={{ paging: true, search: false, pageSize: 10 }}
|
||||
data={data}
|
||||
emptyContent={<LinearProgress />}
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
/>
|
||||
<Snackbar
|
||||
open={isSnackbarOpen}
|
||||
autoHideDuration={6_000}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
onClose={onSnackbarClose}
|
||||
>
|
||||
<MuiAlert onClose={onSnackbarClose} severity="info">
|
||||
{snackbarMessage}
|
||||
</MuiAlert>
|
||||
</Snackbar>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -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 { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites';
|
||||
export const useServiceEntityAnnotations = (entity: Entity) => {
|
||||
const webSiteName =
|
||||
entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION] ?? '';
|
||||
|
||||
return {
|
||||
webSiteName,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 useInterval from 'react-use/lib/useInterval';
|
||||
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
|
||||
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common';
|
||||
import { azureSiteApiRef } from '../api';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const POLLING_INTERVAL = 15000;
|
||||
|
||||
export function useSites({ name }: { name: string }) {
|
||||
const azureApi = useApi(azureSiteApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
const list = useCallback(
|
||||
async () => {
|
||||
return await azureApi.list({
|
||||
name: name,
|
||||
});
|
||||
},
|
||||
[name], // eslint-disable-line react-hooks/exhaustive-deps
|
||||
);
|
||||
const {
|
||||
loading,
|
||||
value: data,
|
||||
error,
|
||||
retry,
|
||||
} = useAsyncRetry<AzureSiteListResponse | null>(async () => {
|
||||
try {
|
||||
const sites = await list();
|
||||
return sites;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e?.message === 'AbortError') {
|
||||
errorApi.post(
|
||||
new Error(
|
||||
'Timeout reaching backend plugin, please add azure-backend plugin',
|
||||
),
|
||||
);
|
||||
}
|
||||
errorApi.post(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useInterval(() => retry(), POLLING_INTERVAL);
|
||||
|
||||
return [
|
||||
{
|
||||
loading,
|
||||
data,
|
||||
error,
|
||||
retry,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
@@ -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 './plugin';
|
||||
export * from './api';
|
||||
export * from './components/AzureSitesOverviewComponent/AzureSitesOverview';
|
||||
@@ -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 { AzureSite } from '@backstage/plugin-azure-sites-common';
|
||||
|
||||
export const entityMock = {
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
'azure.com/microsoft-web-sites': 'func-mock',
|
||||
},
|
||||
name: 'sample-azure-service',
|
||||
description: 'A service for testing Backstage functionality.',
|
||||
uid: 'c009b513-d053-4b3f-9429-8433a145e943',
|
||||
},
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
spec: {
|
||||
type: 'service',
|
||||
owner: 'dev_group@example.com',
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
};
|
||||
|
||||
// https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Web/sites/{{functionsName}}?api-version=2022-03-01
|
||||
export const siteMock: AzureSite = {
|
||||
name: 'func-mock',
|
||||
kind: 'functionapp',
|
||||
resourceGroup: 'mock-resourcegroup',
|
||||
subscription: '00000000-0000-0000-0000-000000000000',
|
||||
tags: {
|
||||
isMock: true,
|
||||
},
|
||||
location: 'West Europe',
|
||||
state: 'Running',
|
||||
href: 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock',
|
||||
logstreamHref:
|
||||
'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock/logStream',
|
||||
usageState: 'Normal',
|
||||
lastModifiedDate: '2022-09-02T11:09:58.9033333',
|
||||
containerSize: 100,
|
||||
};
|
||||
@@ -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 { azureSitesPlugin } from './plugin';
|
||||
|
||||
describe('azure', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(azureSitesPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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,
|
||||
createComponentExtension,
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { azureSiteApiRef, AzureSitesApiBackendClient } from './api';
|
||||
|
||||
const entityContentRouteRef = createRouteRef({
|
||||
id: 'Azure Sites Entity Content',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const azureSitesPlugin = createPlugin({
|
||||
id: 'azureSites',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: azureSiteApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new AzureSitesApiBackendClient({ discoveryApi, identityApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
entityContent: entityContentRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const EntityAzureSitesOverviewWidget = azureSitesPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityAzureSitesOverviewWidget',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import(
|
||||
'./components/AzureSitesOverviewComponent/AzureSitesOverview'
|
||||
).then(m => m.AzureSitesOverviewWidget),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -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: 'azure-sites',
|
||||
});
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
@@ -339,6 +339,33 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/arm-appservice@npm:^13.0.1":
|
||||
version: 13.0.2
|
||||
resolution: "@azure/arm-appservice@npm:13.0.2"
|
||||
dependencies:
|
||||
"@azure/abort-controller": ^1.0.0
|
||||
"@azure/core-auth": ^1.3.0
|
||||
"@azure/core-client": ^1.5.0
|
||||
"@azure/core-lro": ^2.2.0
|
||||
"@azure/core-paging": ^1.2.0
|
||||
"@azure/core-rest-pipeline": ^1.8.0
|
||||
tslib: ^2.2.0
|
||||
checksum: 6d604c492a70a6cdd4d3ad3f634a74228dc081ed4f49128873e76bfa5e65ec8cce18b7b19319fd2b1b4edbd390dbd66c9ac48646f0ed80cae09cd2f1d05fb279
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/arm-resourcegraph@npm:^4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "@azure/arm-resourcegraph@npm:4.2.1"
|
||||
dependencies:
|
||||
"@azure/core-auth": ^1.1.4
|
||||
"@azure/ms-rest-azure-js": ^2.1.0
|
||||
"@azure/ms-rest-js": ^2.2.0
|
||||
tslib: ^1.10.0
|
||||
checksum: a5afea9faef84ed4576fda430301039524aefe6976a54204fe4453e672aa3d13edc7ef5c996ad6293e9afa3a0833549174aaac18ce236c4ec1242698e2c22bd2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-asynciterator-polyfill@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@azure/core-asynciterator-polyfill@npm:1.0.0"
|
||||
@@ -346,6 +373,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-auth@npm:^1.1.4, @azure/core-auth@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@azure/core-auth@npm:1.4.0"
|
||||
dependencies:
|
||||
"@azure/abort-controller": ^1.0.0
|
||||
tslib: ^2.2.0
|
||||
checksum: 1c76c296fe911ad39fc780b033c25a92c41c5a15f011b816d42c13584f627a4dd153dfb4334900ec93eb5b006e14bdda37e8412a7697c5a9636a0abaccffad39
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-auth@npm:^1.3.0":
|
||||
version: 1.3.2
|
||||
resolution: "@azure/core-auth@npm:1.3.2"
|
||||
@@ -371,6 +408,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-client@npm:^1.5.0":
|
||||
version: 1.6.1
|
||||
resolution: "@azure/core-client@npm:1.6.1"
|
||||
dependencies:
|
||||
"@azure/abort-controller": ^1.0.0
|
||||
"@azure/core-auth": ^1.4.0
|
||||
"@azure/core-rest-pipeline": ^1.9.1
|
||||
"@azure/core-tracing": ^1.0.0
|
||||
"@azure/core-util": ^1.0.0
|
||||
"@azure/logger": ^1.0.0
|
||||
tslib: ^2.2.0
|
||||
checksum: 400890a9b5f0c8801ff92005c3cba7bb5124634e321735406731bef33688a79f23d28b49fccdfab90814dade41a13f3d3cb99f80151a2bff17628416e811afb6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-http@npm:^2.0.0":
|
||||
version: 2.2.4
|
||||
resolution: "@azure/core-http@npm:2.2.4"
|
||||
@@ -415,6 +467,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-paging@npm:^1.2.0":
|
||||
version: 1.3.0
|
||||
resolution: "@azure/core-paging@npm:1.3.0"
|
||||
dependencies:
|
||||
tslib: ^2.2.0
|
||||
checksum: 3fbf3d6474e2346f7c3ea8344a41bf41e053789efbbd29df78365e9c9ca66e143da3e5407d8c9dd5ddc82a65680185cd6f0ec851c48635776d18a6a605a98e78
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "@azure/core-rest-pipeline@npm:1.5.0"
|
||||
@@ -432,6 +493,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-rest-pipeline@npm:^1.8.0, @azure/core-rest-pipeline@npm:^1.9.1":
|
||||
version: 1.9.2
|
||||
resolution: "@azure/core-rest-pipeline@npm:1.9.2"
|
||||
dependencies:
|
||||
"@azure/abort-controller": ^1.0.0
|
||||
"@azure/core-auth": ^1.4.0
|
||||
"@azure/core-tracing": ^1.0.1
|
||||
"@azure/core-util": ^1.0.0
|
||||
"@azure/logger": ^1.0.0
|
||||
form-data: ^4.0.0
|
||||
http-proxy-agent: ^5.0.0
|
||||
https-proxy-agent: ^5.0.0
|
||||
tslib: ^2.2.0
|
||||
uuid: ^8.3.0
|
||||
checksum: cdfdaf8bdb0778f40ecfeb8b5da0b4f8643d641b75bb5869485afd38f7c3127ab088ac2fe2980df03d28fb6715928e04560343ee6416246aebe75443c4dd2906
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-tracing@npm:1.0.0-preview.13":
|
||||
version: 1.0.0-preview.13
|
||||
resolution: "@azure/core-tracing@npm:1.0.0-preview.13"
|
||||
@@ -442,7 +521,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/core-tracing@npm:^1.0.0":
|
||||
"@azure/core-tracing@npm:^1.0.0, @azure/core-tracing@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@azure/core-tracing@npm:1.0.1"
|
||||
dependencies:
|
||||
@@ -493,6 +572,34 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/ms-rest-azure-js@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@azure/ms-rest-azure-js@npm:2.1.0"
|
||||
dependencies:
|
||||
"@azure/core-auth": ^1.1.4
|
||||
"@azure/ms-rest-js": ^2.2.0
|
||||
tslib: ^1.10.0
|
||||
checksum: 0e6d6700f4ee0ad14383642b02fe86b33f4034aaff38499374ed5be59154e869ee231790d20b7557622ebcdfa9eed45dc2559391a79dee51c849a8ced0c86fed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/ms-rest-js@npm:^2.2.0":
|
||||
version: 2.6.2
|
||||
resolution: "@azure/ms-rest-js@npm:2.6.2"
|
||||
dependencies:
|
||||
"@azure/core-auth": ^1.1.4
|
||||
abort-controller: ^3.0.0
|
||||
form-data: ^2.5.0
|
||||
node-fetch: ^2.6.7
|
||||
tough-cookie: ^3.0.1
|
||||
tslib: ^1.10.0
|
||||
tunnel: 0.0.6
|
||||
uuid: ^8.3.2
|
||||
xml2js: ^0.4.19
|
||||
checksum: de650dfc3928a2036923fb3bf50e0324f9f36f5d320ca6d128005b1345ad80b39ab3410073ac35fb137dec4c7d799c602b1cafdf9573b3432b6b8eee8a1e39f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/msal-browser@npm:^2.26.0":
|
||||
version: 2.27.0
|
||||
resolution: "@azure/msal-browser@npm:2.27.0"
|
||||
@@ -4138,6 +4245,66 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-azure-sites-backend@workspace:^, @backstage/plugin-azure-sites-backend@workspace:plugins/azure-sites-backend":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-azure-sites-backend@workspace:plugins/azure-sites-backend"
|
||||
dependencies:
|
||||
"@azure/arm-appservice": ^13.0.1
|
||||
"@azure/arm-resourcegraph": ^4.2.1
|
||||
"@azure/identity": ^2.1.0
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/plugin-azure-sites-common": "workspace:^"
|
||||
"@types/express": ^4.17.6
|
||||
"@types/supertest": ^2.0.8
|
||||
express: ^4.17.1
|
||||
express-promise-router: ^4.1.0
|
||||
msw: ^0.47.0
|
||||
node-fetch: ^2.6.7
|
||||
winston: ^3.2.1
|
||||
yn: ^4.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-azure-sites-common@workspace:^, @backstage/plugin-azure-sites-common@workspace:plugins/azure-sites-common":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-azure-sites-common@workspace:plugins/azure-sites-common"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-azure-sites@workspace:^, @backstage/plugin-azure-sites@workspace:plugins/azure-sites":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-azure-sites@workspace:plugins/azure-sites"
|
||||
dependencies:
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-app-api": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/plugin-azure-sites-common": "workspace:^"
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.12.2
|
||||
"@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": ^16.11.26
|
||||
cross-fetch: ^3.1.5
|
||||
luxon: ^3.0.0
|
||||
msw: ^0.47.0
|
||||
react-use: ^17.2.4
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-badges-backend@workspace:^, @backstage/plugin-badges-backend@workspace:plugins/badges-backend":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-badges-backend@workspace:plugins/badges-backend"
|
||||
@@ -21868,6 +22035,7 @@ __metadata:
|
||||
"@backstage/plugin-apache-airflow": "workspace:^"
|
||||
"@backstage/plugin-api-docs": "workspace:^"
|
||||
"@backstage/plugin-azure-devops": "workspace:^"
|
||||
"@backstage/plugin-azure-sites": "workspace:^"
|
||||
"@backstage/plugin-badges": "workspace:^"
|
||||
"@backstage/plugin-catalog-common": "workspace:^"
|
||||
"@backstage/plugin-catalog-graph": "workspace:^"
|
||||
@@ -21973,6 +22141,7 @@ __metadata:
|
||||
"@backstage/plugin-auth-backend": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/plugin-azure-devops-backend": "workspace:^"
|
||||
"@backstage/plugin-azure-sites-backend": "workspace:^"
|
||||
"@backstage/plugin-badges-backend": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend": "workspace:^"
|
||||
"@backstage/plugin-code-coverage-backend": "workspace:^"
|
||||
@@ -25064,6 +25233,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip-regex@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "ip-regex@npm:2.1.0"
|
||||
checksum: 331d95052aa53ce245745ea0fc3a6a1e2e3c8d6da65fa8ea52bf73768c1b22a9ac50629d1d2b08c04e7b3ac4c21b536693c149ce2c2615ee4796030e5b3e3cba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip@npm:^1.1.5":
|
||||
version: 1.1.5
|
||||
resolution: "ip@npm:1.1.5"
|
||||
@@ -37528,6 +37704,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tough-cookie@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "tough-cookie@npm:3.0.1"
|
||||
dependencies:
|
||||
ip-regex: ^2.1.0
|
||||
psl: ^1.1.28
|
||||
punycode: ^2.1.1
|
||||
checksum: 796f6239bce5674a1267b19f41972a2602a2a23715817237b5922b0dc2343512512eea7d41d29210a4ec545f8ef32173bbbf01277dd8ec3ae3841b19cbe69f67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tough-cookie@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "tough-cookie@npm:4.0.0"
|
||||
@@ -37729,7 +37916,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3":
|
||||
"tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3":
|
||||
version: 1.14.1
|
||||
resolution: "tslib@npm:1.14.1"
|
||||
checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd
|
||||
|
||||
Reference in New Issue
Block a user