Merge branch 'master' into k8s-backend-skipTLSVerify-configurable
Signed-off-by: Travis Truman <trumant@gmail.com>
This commit is contained in:
@@ -55,8 +55,7 @@ proxy:
|
||||
target: 'https://api.bitrise.io/v0.1'
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
$env: BITRISE_AUTH_TOKEN
|
||||
Authorization: ${BITRISE_AUTH_TOKEN}
|
||||
```
|
||||
|
||||
Learn on https://devcenter.bitrise.io/api/authentication how to create a new Bitrise token.
|
||||
|
||||
@@ -42,7 +42,7 @@ export class CatalogClientWrapper implements CatalogApi {
|
||||
}
|
||||
|
||||
async getLocationById(
|
||||
id: String,
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
return await this.client.getLocationById(id, {
|
||||
|
||||
@@ -43,8 +43,7 @@ proxy:
|
||||
'/circleci/api':
|
||||
target: https://circleci.com/api/v1.1
|
||||
headers:
|
||||
Circle-Token:
|
||||
$env: CIRCLECI_AUTH_TOKEN
|
||||
Circle-Token: ${CIRCLECI_AUTH_TOKEN}
|
||||
```
|
||||
|
||||
5. Get and provide `CIRCLECI_AUTH_TOKEN` as env variable (https://circleci.com/docs/api/#add-an-api-token)
|
||||
|
||||
@@ -50,9 +50,7 @@ proxy:
|
||||
target: https://app.fossa.io/api
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
# Content: 'token <your-fossa-api-token>'
|
||||
$env: FOSSA_AUTH_HEADER
|
||||
Authorization: token ${FOSSA_API_TOKEN}
|
||||
|
||||
# if you have a fossa organization, configure your id here
|
||||
fossa:
|
||||
|
||||
@@ -29,15 +29,13 @@ proxy:
|
||||
target: 'http://localhost:8080' # your Jenkins URL
|
||||
changeOrigin: true
|
||||
headers:
|
||||
Authorization:
|
||||
$env: JENKINS_BASIC_AUTH_HEADER
|
||||
Authorization: Basic ${JENKINS_BASIC_AUTH_HEADER}
|
||||
```
|
||||
|
||||
4. Add an environment variable which contains the Jenkins credentials, (note: use an API token not your password). Here user is the name of the user created in Jenkins.
|
||||
|
||||
```shell
|
||||
HEADER=$(echo -n user:api-token | base64)
|
||||
export JENKINS_BASIC_AUTH_HEADER="Basic $HEADER"
|
||||
export JENKINS_BASIC_AUTH_HEADER=$(echo -n user:api-token | base64)
|
||||
```
|
||||
|
||||
5. Run app with `yarn start`
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator
|
||||
|
||||
export class KubernetesAuthTranslatorGenerator {
|
||||
static getKubernetesAuthTranslatorInstance(
|
||||
authProvider: String,
|
||||
authProvider: string,
|
||||
): KubernetesAuthTranslator {
|
||||
switch (authProvider) {
|
||||
case 'google': {
|
||||
|
||||
@@ -34,8 +34,8 @@ const mockFetch = (mock: jest.Mock) => {
|
||||
};
|
||||
|
||||
function generateMockResourcesAndErrors(
|
||||
serviceId: String,
|
||||
clusterName: String,
|
||||
serviceId: string,
|
||||
clusterName: string,
|
||||
) {
|
||||
if (clusterName === 'empty-cluster') {
|
||||
return {
|
||||
|
||||
@@ -15,8 +15,7 @@ proxy:
|
||||
'/newrelic/apm/api':
|
||||
target: https://api.newrelic.com/v2
|
||||
headers:
|
||||
X-Api-Key:
|
||||
$env: NEW_RELIC_REST_API_KEY
|
||||
X-Api-Key: ${NEW_RELIC_REST_API_KEY}
|
||||
```
|
||||
|
||||
In your production deployment of Backstage, you would also need to ensure that
|
||||
|
||||
@@ -8,8 +8,7 @@ The following values are read from the configuration file.
|
||||
|
||||
```yaml
|
||||
rollbar:
|
||||
accountToken:
|
||||
$env: ROLLBAR_ACCOUNT_TOKEN
|
||||
accountToken: ${ROLLBAR_ACCOUNT_TOKEN}
|
||||
```
|
||||
|
||||
_NOTE: The `ROLLBAR_ACCOUNT_TOKEN` environment variable must be set to a read
|
||||
|
||||
@@ -45,8 +45,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
rollbar:
|
||||
organization: organization-name
|
||||
# used by rollbar-backend
|
||||
accountToken:
|
||||
$env: ROLLBAR_ACCOUNT_TOKEN
|
||||
accountToken: ${ROLLBAR_ACCOUNT_TOKEN}
|
||||
```
|
||||
|
||||
6. Annotate entities with the rollbar project slug
|
||||
|
||||
@@ -50,6 +50,7 @@ spec:
|
||||
values:
|
||||
name: '{{ parameters.name }}'
|
||||
owner: '{{ parameters.owner }}'
|
||||
destination: '{{ parseRepoUrl parameters.repoUrl }}'
|
||||
|
||||
- id: fetch-docs
|
||||
name: Fetch Docs
|
||||
|
||||
@@ -2,6 +2,7 @@ apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: {{cookiecutter.name | jsonify}}
|
||||
github.com/project-slug: {{cookiecutter.destination.owner + "/" + cookiecutter.destination.repo}}
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: experimental
|
||||
|
||||
@@ -30,8 +30,14 @@ export const getRepoSourceDirectory = (
|
||||
}
|
||||
return workspacePath;
|
||||
};
|
||||
export type RepoSpec = {
|
||||
repo: string;
|
||||
host: string;
|
||||
owner: string;
|
||||
organization?: string;
|
||||
};
|
||||
|
||||
export const parseRepoUrl = (repoUrl: string) => {
|
||||
export const parseRepoUrl = (repoUrl: string): RepoSpec => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(`https://${repoUrl}`);
|
||||
@@ -55,7 +61,7 @@ export const parseRepoUrl = (repoUrl: string) => {
|
||||
);
|
||||
}
|
||||
|
||||
const organization = parsed.searchParams.get('organization');
|
||||
const organization = parsed.searchParams.get('organization') ?? undefined;
|
||||
|
||||
return { host, owner, repo, organization };
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ConfigReader, JsonObject } from '@backstage/config';
|
||||
import { StorageTaskBroker } from './StorageTaskBroker';
|
||||
import { DatabaseTaskStore } from './DatabaseTaskStore';
|
||||
import { createTemplateAction, TemplateActionRegistry } from '../actions';
|
||||
import { RepoSpec } from '../actions/builtin/publish/util';
|
||||
|
||||
async function createStore(): Promise<DatabaseTaskStore> {
|
||||
const manager = SingleConnectionDatabaseManager.fromConfig(
|
||||
@@ -41,20 +42,24 @@ async function createStore(): Promise<DatabaseTaskStore> {
|
||||
|
||||
describe('TaskWorker', () => {
|
||||
let storage: DatabaseTaskStore;
|
||||
let actionRegistry = new TemplateActionRegistry();
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = await createStore();
|
||||
});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
const actionRegistry = new TemplateActionRegistry();
|
||||
actionRegistry.register({
|
||||
id: 'test-action',
|
||||
handler: async ctx => {
|
||||
ctx.output('testOutput', 'winning');
|
||||
},
|
||||
beforeEach(() => {
|
||||
actionRegistry = new TemplateActionRegistry();
|
||||
actionRegistry.register({
|
||||
id: 'test-action',
|
||||
handler: async ctx => {
|
||||
ctx.output('testOutput', 'winning');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
it('should fail when action does not exist', async () => {
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const taskWorker = new TaskWorker({
|
||||
@@ -166,4 +171,194 @@ describe('TaskWorker', () => {
|
||||
const event = events.find(e => e.type === 'completion');
|
||||
expect((event?.body?.output as JsonObject).result).toBe('winning');
|
||||
});
|
||||
|
||||
it('should parse strings as objects if possible', async () => {
|
||||
const inputAction = createTemplateAction<{
|
||||
address: { line1: string };
|
||||
list: string[];
|
||||
address2: string;
|
||||
}>({
|
||||
id: 'test-input',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['address'],
|
||||
properties: {
|
||||
address: {
|
||||
title: 'address',
|
||||
description: 'Enter name',
|
||||
type: 'object',
|
||||
properties: {
|
||||
line1: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
address2: {
|
||||
type: 'string',
|
||||
},
|
||||
list: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
if (ctx.input.list.length !== 1) {
|
||||
throw new Error(
|
||||
`expected list to have length "1" got ${ctx.input.list.length}`,
|
||||
);
|
||||
}
|
||||
if (ctx.input.address.line1 !== 'line 1') {
|
||||
throw new Error(
|
||||
`expected address.line1 to be "line 1" got ${ctx.input.address.line1}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx.input.address2 !== '{"not valid"}') {
|
||||
throw new Error(
|
||||
`expected address2 to be "{"not valid"}" got ${ctx.input.address2}`,
|
||||
);
|
||||
}
|
||||
ctx.output('address', ctx.input.address.line1);
|
||||
},
|
||||
});
|
||||
actionRegistry.register(inputAction);
|
||||
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const taskWorker = new TaskWorker({
|
||||
logger,
|
||||
workingDirectory: os.tmpdir(),
|
||||
actionRegistry,
|
||||
taskBroker: broker,
|
||||
});
|
||||
|
||||
const { taskId } = await broker.dispatch({
|
||||
steps: [
|
||||
{
|
||||
id: 'test-input',
|
||||
name: 'test-input',
|
||||
action: 'test-input',
|
||||
input: {
|
||||
address: JSON.stringify({ line1: 'line 1' }),
|
||||
list: JSON.stringify(['hey!']),
|
||||
address2: '{"not valid"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
output: {
|
||||
result: '{{ steps.test-input.output.address }}',
|
||||
},
|
||||
values: {},
|
||||
});
|
||||
|
||||
const task = await broker.claim();
|
||||
await taskWorker.runOneTask(task);
|
||||
|
||||
const { events } = await storage.listEvents({ taskId });
|
||||
const event = events.find(e => e.type === 'completion');
|
||||
|
||||
expect((event?.body?.output as JsonObject).result).toBe('line 1');
|
||||
});
|
||||
|
||||
// TODO(blam): Can delete this test when we make the helpers a public API
|
||||
it('should provide a repoUrlParse helper for the templates', async () => {
|
||||
const inputAction = createTemplateAction<{
|
||||
destination: RepoSpec;
|
||||
}>({
|
||||
id: 'test-input',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['destination'],
|
||||
properties: {
|
||||
destination: {
|
||||
title: 'destination',
|
||||
type: 'object',
|
||||
properties: {
|
||||
repo: {
|
||||
type: 'string',
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
},
|
||||
owner: {
|
||||
type: 'string',
|
||||
},
|
||||
organization: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
ctx.output('host', ctx.input.destination.host);
|
||||
ctx.output('repo', ctx.input.destination.repo);
|
||||
ctx.output('owner', ctx.input.destination.owner);
|
||||
|
||||
if (ctx.input.destination.host !== 'github.com') {
|
||||
throw new Error(
|
||||
`expected host to be "github.com" got ${ctx.input.destination.host}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx.input.destination.repo !== 'repo') {
|
||||
throw new Error(
|
||||
`expected repo to be "repo" got ${ctx.input.destination.repo}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx.input.destination.owner !== 'owner') {
|
||||
throw new Error(
|
||||
`expected repo to be "owner" got ${ctx.input.destination.owner}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
actionRegistry.register(inputAction);
|
||||
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const taskWorker = new TaskWorker({
|
||||
logger,
|
||||
workingDirectory: os.tmpdir(),
|
||||
actionRegistry,
|
||||
taskBroker: broker,
|
||||
});
|
||||
|
||||
const { taskId } = await broker.dispatch({
|
||||
steps: [
|
||||
{
|
||||
id: 'test-input',
|
||||
name: 'test-input',
|
||||
action: 'test-input',
|
||||
input: {
|
||||
destination: '{{ parseRepoUrl parameters.repoUrl }}',
|
||||
},
|
||||
},
|
||||
],
|
||||
output: {
|
||||
host: '{{ steps.test-input.output.host }}',
|
||||
repo: '{{ steps.test-input.output.repo }}',
|
||||
owner: '{{ steps.test-input.output.owner }}',
|
||||
},
|
||||
values: {
|
||||
repoUrl: 'github.com?repo=repo&owner=owner',
|
||||
},
|
||||
});
|
||||
|
||||
const task = await broker.claim();
|
||||
await taskWorker.runOneTask(task);
|
||||
|
||||
const { events } = await storage.listEvents({ taskId });
|
||||
const event = events.find(e => e.type === 'completion');
|
||||
|
||||
expect((event?.body?.output as JsonObject).host).toBe('github.com');
|
||||
expect((event?.body?.output as JsonObject).repo).toBe('repo');
|
||||
expect((event?.body?.output as JsonObject).owner).toBe('owner');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,8 +23,9 @@ import { TaskBroker, Task } from './types';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { TemplateActionRegistry } from '../actions/TemplateActionRegistry';
|
||||
import * as handlebars from 'handlebars';
|
||||
import * as Handlebars from 'handlebars';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { parseRepoUrl } from '../actions/builtin/publish/util';
|
||||
|
||||
type Options = {
|
||||
logger: Logger;
|
||||
@@ -34,7 +35,20 @@ type Options = {
|
||||
};
|
||||
|
||||
export class TaskWorker {
|
||||
constructor(private readonly options: Options) {}
|
||||
private readonly handlebars: typeof Handlebars;
|
||||
|
||||
constructor(private readonly options: Options) {
|
||||
this.handlebars = Handlebars.create();
|
||||
|
||||
// TODO(blam): this should be a public facing API but it's a little
|
||||
// scary right now, so we're going to lock it off like the component API is
|
||||
// in the frontend until we can work out a nice way to do it.
|
||||
this.handlebars.registerHelper('parseRepoUrl', repoUrl => {
|
||||
return JSON.stringify(parseRepoUrl(repoUrl));
|
||||
});
|
||||
|
||||
this.handlebars.registerHelper('json', obj => JSON.stringify(obj));
|
||||
}
|
||||
|
||||
start() {
|
||||
(async () => {
|
||||
@@ -102,13 +116,29 @@ export class TaskWorker {
|
||||
step.input &&
|
||||
JSON.parse(JSON.stringify(step.input), (_key, value) => {
|
||||
if (typeof value === 'string') {
|
||||
return handlebars.compile(value, {
|
||||
const templated = this.handlebars.compile(value, {
|
||||
noEscape: true,
|
||||
strict: true,
|
||||
data: false,
|
||||
preventIndent: true,
|
||||
})(templateCtx);
|
||||
|
||||
// If it smells like a JSON object then give it a parse as an object and if it fails return the string
|
||||
if (
|
||||
(templated.startsWith('{') && templated.endsWith('}')) ||
|
||||
(templated.startsWith('[') && templated.endsWith(']'))
|
||||
) {
|
||||
try {
|
||||
// Don't recursively JSON parse the values of this string.
|
||||
// Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else
|
||||
return JSON.parse(templated);
|
||||
} catch {
|
||||
return templated;
|
||||
}
|
||||
}
|
||||
return templated;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
@@ -171,7 +201,7 @@ export class TaskWorker {
|
||||
JSON.stringify(task.spec.output),
|
||||
(_key, value) => {
|
||||
if (typeof value === 'string') {
|
||||
return handlebars.compile(value, {
|
||||
return this.handlebars.compile(value, {
|
||||
noEscape: true,
|
||||
strict: true,
|
||||
data: false,
|
||||
|
||||
@@ -70,9 +70,7 @@ proxy:
|
||||
target: https://sentry.io/api/
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
# Content: 'Bearer <your-sentry-token>'
|
||||
$env: SENTRY_TOKEN
|
||||
Authorization: Bearer ${SENTRY_TOKEN}
|
||||
|
||||
sentry:
|
||||
organization: <your-organization>
|
||||
|
||||
+11
-13
@@ -54,10 +54,9 @@ proxy:
|
||||
target: https://sonarcloud.io/api
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
# Content: 'Basic base64("<api-key>:")' <-- note the trailing ':'
|
||||
# Example: Basic bXktYXBpLWtleTo=
|
||||
$env: SONARQUBE_AUTH_HEADER
|
||||
Authorization: Basic ${SONARQUBE_AUTH}
|
||||
# Content: 'base64("<api-key>:")' <-- note the trailing ':'
|
||||
# Example: bXktYXBpLWtleTo=
|
||||
```
|
||||
|
||||
**SonarQube**
|
||||
@@ -70,20 +69,19 @@ proxy:
|
||||
target: https://your.sonarqube.instance.com/api
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
# Environmental variable: SONARQUBE_AUTH_HEADER
|
||||
# Value: 'Basic base64("<sonar-auth-token>:")'
|
||||
# Encode the "<sonar-auth-token>:" string using base64 encoder.
|
||||
# Note the trailing colon (:) at the end of the token.
|
||||
# Example environmental config: SONARQUBE_AUTH_HEADER=Basic bXktYXBpLWtleTo=
|
||||
# Fetch the sonar-auth-token from https://sonarcloud.io/account/security/
|
||||
$env: SONARQUBE_AUTH_HEADER
|
||||
Authorization: Basic ${SONARQUBE_AUTH}
|
||||
# Environmental variable: SONARQUBE_AUTH
|
||||
# Value: 'base64("<sonar-auth-token>:")'
|
||||
# Encode the "<sonar-auth-token>:" string using base64 encoder.
|
||||
# Note the trailing colon (:) at the end of the token.
|
||||
# Example environmental config: SONARQUBE_AUTH=bXktYXBpLWtleTo=
|
||||
# Fetch the sonar-auth-token from https://sonarcloud.io/account/security/
|
||||
|
||||
sonarQube:
|
||||
baseUrl: https://your.sonarqube.instance.com
|
||||
```
|
||||
|
||||
5. Get and provide `SONARQUBE_AUTH_HEADER` as env variable (https://sonarcloud.io/account/security or https://docs.sonarqube.org/latest/user-guide/user-token/)
|
||||
5. Get and provide `SONARQUBE_AUTH` as an env variable (https://sonarcloud.io/account/security or https://docs.sonarqube.org/latest/user-guide/user-token/)
|
||||
|
||||
6. Run the following commands in the root folder of the project to install and compile the changes.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
|
||||
In order to be able to perform certain action (create-acknowledge-resolve an action), you need to provide a REST Endpoint.
|
||||
|
||||
To enable the REST Endpoint integration you can go on https://portal.victorops.com/ inside Integrations > 3rd Party Integrations > REST – Generic.
|
||||
To enable the REST Endpoint integration you can go on https://portal.victorops.com/ inside Integrations > 3rd Party Integrations > REST – Generic.
|
||||
You can now copy the URL to notify: `<SPLUNK_ON_CALL_REST_ENDPOINT>/$routing_key`
|
||||
|
||||
In `app-config.yaml`:
|
||||
@@ -69,10 +69,8 @@ proxy:
|
||||
'/splunk-on-call':
|
||||
target: https://api.victorops.com/api-public
|
||||
headers:
|
||||
X-VO-Api-Id:
|
||||
$env: SPLUNK_ON_CALL_API_ID
|
||||
X-VO-Api-Key:
|
||||
$env: SPLUNK_ON_CALL_API_KEY
|
||||
X-VO-Api-Id: ${SPLUNK_ON_CALL_API_ID}
|
||||
X-VO-Api-Key: ${SPLUNK_ON_CALL_API_KEY}
|
||||
```
|
||||
|
||||
In addition, to make certain API calls (trigger-resolve-acknowledge an incident) you need to add the `PATCH` method to the backend `cors` methods list: `[GET, POST, PUT, DELETE, PATCH]`.
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
ConfigReader,
|
||||
} from '@backstage/core';
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
@@ -26,7 +32,16 @@ describe('TechDocs Home', () => {
|
||||
getEntities: async () => ({ items: [] }),
|
||||
};
|
||||
|
||||
const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi);
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
organization: {
|
||||
name: 'My Company',
|
||||
},
|
||||
});
|
||||
|
||||
const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi).with(
|
||||
configApiRef,
|
||||
configApi,
|
||||
);
|
||||
|
||||
it('should render a TechDocs home page', async () => {
|
||||
await renderInTestApp(
|
||||
@@ -38,7 +53,7 @@ describe('TechDocs Home', () => {
|
||||
// Header
|
||||
expect(await screen.findByText('Documentation')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText(/Documentation available in Backstage/i),
|
||||
await screen.findByText(/Documentation available in My Company/i),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Explore Content
|
||||
|
||||
@@ -21,6 +21,8 @@ import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog-react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
Content,
|
||||
Header,
|
||||
HeaderTabs,
|
||||
@@ -36,6 +38,7 @@ import { OwnedContent } from './OwnedContent';
|
||||
export const TechDocsHome = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
const catalogApi: CatalogApi = useApi(catalogApiRef);
|
||||
const configApi: ConfigApi = useApi(configApiRef);
|
||||
|
||||
const tabs = [{ label: 'Overview' }, { label: 'Owned Documents' }];
|
||||
|
||||
@@ -46,13 +49,14 @@ export const TechDocsHome = () => {
|
||||
});
|
||||
});
|
||||
|
||||
const generatedSubtitle = `Documentation available in ${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
}`;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
<Header
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
/>
|
||||
<Header title="Documentation" subtitle={generatedSubtitle} />
|
||||
<Content>
|
||||
<Progress />
|
||||
</Content>
|
||||
@@ -63,10 +67,7 @@ export const TechDocsHome = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
<Header
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
/>
|
||||
<Header title="Documentation" subtitle={generatedSubtitle} />
|
||||
<Content>
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
@@ -81,10 +82,7 @@ export const TechDocsHome = () => {
|
||||
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
<Header
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
/>
|
||||
<Header title="Documentation" subtitle={generatedSubtitle} />
|
||||
<HeaderTabs
|
||||
selectedIndex={selectedTab}
|
||||
onChange={index => setSelectedTab(index)}
|
||||
|
||||
@@ -22,10 +22,8 @@ const EXAMPLE = `auth:
|
||||
providers:
|
||||
google:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_GOOGLE_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_GOOGLE_CLIENT_SECRET
|
||||
clientId: \${AUTH_GOOGLE_CLIENT_ID}
|
||||
clientSecret: \${AUTH_GOOGLE_CLIENT_SECRET}
|
||||
`;
|
||||
|
||||
export const EmptyProviders = () => (
|
||||
|
||||
Reference in New Issue
Block a user