Merge branch 'master' into feat-urlreader-issearchurl

This commit is contained in:
Thomas Cardonne
2025-01-27 16:36:43 +01:00
73 changed files with 1242 additions and 295 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-kubernetes-cluster': patch
'@backstage/plugin-kubernetes-react': patch
---
Improved rendering of Kubernetes resources' metadata.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Added `nestedValuesAsYaml` option to `StructuredMetadataTable` to render data as yaml.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Fixed an issue where invalid expressions or non-object values in `step.each` caused an error.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Add missing route ref to the `/alpha` entity content extension.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
The log message written when plugins fail to initialize now includes the error that caused the plugin startup to fail.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/core-components': patch
'@backstage/plugin-scaffolder': minor
---
Updating the `TaskLogStream` to take up all space in a running task, and also show the last line of the log by default
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fixed the file path pattern of many static assets output as part of the frontend build process, where there was an extra `.` before the extension, leading to names like `image-af7946b..png`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-ldap': patch
---
Make `ldapOrg` config key optional
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Allow route references to be installed in multiple app instances as long as their name is the same.
@@ -167,9 +167,7 @@ Here's a view when buttons are responsive.
py={4}
open
preview={<ButtonResponsive />}
code={`<Button
variant={{ xs: 'primary', sm: 'secondary', md: 'tertiary' }}
size={{ xs: 'small', sm: 'medium' }}>
Button
</Button>`}
code={`<Button variant={{ initial: 'primary', lg: 'secondary' }}>
Responsive Button
</Button>`}
/>
@@ -93,5 +93,7 @@ on the screen size.
py={4}
open
preview={<HeadingResponsive />}
code={`<Heading variant={{ xs: 'title1', md: 'title2' }}>Responsive</Heading>`}
code={`<Heading variant={{ initial: 'title2', lg: 'title1' }}>
Responsive heading
</Heading>`}
/>
@@ -1 +1 @@
# Table
# Table (Coming soon)
@@ -137,5 +137,7 @@ on the screen size.
<Snippet
open
preview={<TextResponsive />}
code={`<Text variant={{ xs: 'label', md: 'body' }}>Responsive</Text>`}
code={`<Text variant={{ initial: 'body', lg: 'subtitle' }}>
Responsive text
</Text>`}
/>
+443 -112
View File
@@ -1,67 +1,70 @@
import { CodeBlock } from '@/components/CodeBlock';
import * as Table from '@/components/Table';
import { Chip } from '@/components/Chip';
import { customTheme } from '@/snippets/_snippets';
# Theming
Backstage ships with a default theme with a light and dark mode variant. The
themes are provided as a part of the `@backstage/canon` package, which also
includes utilities for customizing the default theme, or creating completely
new themes.
Canon's theming is built entirely on CSS, without relying on any CSS-in-JS libraries.
At its core, it provides a solid default theme that is easily customizable using a
comprehensive set of CSS variables. Additionally, it enables anyone to adapt the design
to their specific needs. Each component comes with fixed class names, making customization
even more straightforward.
## Light & Dark modes
By default we are supporting both light and dark modes. Each user can opt to
choose what theme they want to use or to use their system decide what theme to
use. If you want to create your own theme, you will have to set both light and
dark themes following the instructions below. If you only set one of them, the
other mode will fallback to the default theme.
By default, Canon supports both light and dark modes using the `data-theme` attribute.
The light theme is applied by default if no `data-theme` attribute is specified. To create
a custom theme, you'll need to define both light and dark modes as outlined below. If
only one mode is defined, the other will fall back to the default theme.
## How to create your own theme
To create your own theme, you will have to define the variables below. To do
that, create a theme.css file and import it in your application. Here's an
example below on how to set your light and dark mode.
In our [started guide](/), we ask you to import two css files. The `core.css` file includes
the default set of variables. We recommend to keep this file in place and add your own theme
on top of it. `core.css` also include an opinionated reset. If you decided to remove `core.css`
you will have to provide your own reset css.
<CodeBlock
lang="css"
code={`/** Light theme **/
[data-theme='light'] {
--canon-bg-accent: #1ed760;
--canon-bg: #fff;
--canon-bg-elevated: #f5f5f5;
--canon-bg: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #f0f0f0;
--canon-text-secondary: #666;
--canon-font-regular: 'Geist', serif;
--canon-font-mono: 'Monospace', monospace;
/* ... other values */
}
/** Dark theme **/
[data-theme='dark'] {
--canon-bg-accent: #1ed760;
--canon-bg: #000;
--canon-bg-elevated: #f5f5f5;
--canon-bg: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #fff;
--canon-text-secondary: #666;
--canon-font-regular: 'Geist', serif;
--canon-font-mono: 'Monospace', monospace;
/* ... other values */
}
`}
/>
Here's an example of how your theme.css file should look like:
## Colors
<CodeBlock lang="css" code={customTheme} />
We provide a set of generic colours tokens that we use across Canon. By
changing these colours you can easily change the look and feel of your
application to match your brand.
## Available CSS variables
### Core background colors
These colors are used for the background of your application. We are mostly using for now a
single elevated background for panels. `--canon-bg` should mostly use as the main background
color of your app.
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg</Chip>
</Table.Cell>
<Table.Cell>The background color of your Backstage instance.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-elevated</Chip>
</Table.Cell>
<Table.Cell>Use for any panels or elevated surfaces.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Component background colors
These colors are meants for interactive components like buttons, callout avatar, chips, ...
We have two main level of background you can customise. The accent one is mostly used as a primary option
for interactive components. The tint one is used for secondary components like callouts, avatars, ...
<Table.Root>
<Table.Header>
@@ -75,54 +78,254 @@ application to match your brand.
<Table.Cell>
<Chip head>--canon-bg-accent</Chip>
</Table.Cell>
<Table.Cell>The accent color for the theme.</Table.Cell>
<Table.Cell>Used mostly for primary interactive components.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg</Chip>
<Chip head>--canon-bg-accent-hover</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are hovered.
</Table.Cell>
<Table.Cell>The background color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-elevated</Chip>
<Chip head>--canon-bg-accent-focus</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are in focus.
</Table.Cell>
<Table.Cell>The first surface color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg</Chip>
<Chip head>--canon-bg-accent-disabled</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are disabled.
</Table.Cell>
<Table.Cell>The second surface color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-outline</Chip>
<Chip head>--canon-bg-tint</Chip>
</Table.Cell>
<Table.Cell>The outline color for the theme.</Table.Cell>
<Table.Cell>Used for secondary interactive components.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-outline-focus</Chip>
<Chip head>--canon-bg-tint-hover</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are hovered.
</Table.Cell>
<Table.Cell>The outline focus color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-text-primary</Chip>
<Chip head>--canon-bg-tint-focus</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are active.
</Table.Cell>
<Table.Cell>The primary text color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-text-secondary</Chip>
<Chip head>--canon-bg-tint-disabled</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are disabled.
</Table.Cell>
<Table.Cell>The secondary text color for the theme.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
## Typography
### Status background colors
These backgrounds are intended for non-interactive elements to visually convey a specific state.
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-danger</Chip>
</Table.Cell>
<Table.Cell>Used to show errors informations.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-warning</Chip>
</Table.Cell>
<Table.Cell>Used to show warnings informations.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-success</Chip>
</Table.Cell>
<Table.Cell>Used to show success informations.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Foreground colors
Foreground colours are meant to work in pair with a background colours. Typeically this would work
for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors
are prefixed with `fg` to make it easier to identify.
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-text-primary</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-text-secondary</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-link</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-link-hover</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-accent</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-accent`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-tint</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-tint`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-danger</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-danger`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-warning</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-warning`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-success</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-success`.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Border colors
These border colors are mostly meant to be used as borders on top of any components with
low contrast to help as a separator with the different background colors.
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-elevated`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-hover</Chip>
</Table.Cell>
<Table.Cell>
Used when the component is interactive and hovered.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-focus</Chip>
</Table.Cell>
<Table.Cell>
Used when the component is interactive and focused.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-tint</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-tint`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-tint-hover</Chip>
</Table.Cell>
<Table.Cell>Used when the component is hovered.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-tint-focus</Chip>
</Table.Cell>
<Table.Cell>Used when the component is in focus.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-danger</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-danger`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-warning</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-warning`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-success</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-success`.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
## Font families
We have two fonts that we use across Canon. The first one is the sans-serif
font that we use for the body of the application. The second one is the
@@ -151,12 +354,9 @@ monospace font that we use for code blocks and tables.
</Table.Body>
</Table.Root>
## Spacing
## Font weights
Our default spacing system is made to work in most scenarios. We have 7 scale
values from `xxs` to `xxl`. We use the values on padding and margin in our
layout components mostly. If you prefer to use a different spacing system, you
can do that by changing the values below.
We have two font weights that we use across Canon. Regular or Bold
<Table.Root>
<Table.Header>
@@ -168,53 +368,184 @@ can do that by changing the values below.
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-unit</Chip>
</Table.Cell>
<Table.Cell>
The base unit for the spacing system. Default value is `1em`
<Chip head>--canon-font-weight-regular</Chip>
</Table.Cell>
<Table.Cell>The regular font weight for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-xxs</Chip>
<Chip head>--canon-font-weight-bold</Chip>
</Table.Cell>
<Table.Cell>Default value is `0.25 x space unit`</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-xs</Chip>
</Table.Cell>
<Table.Cell>Default value is `0.5 x space unit`</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-sm</Chip>
</Table.Cell>
<Table.Cell>Default value is `0.75 x space unit`</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-md</Chip>
</Table.Cell>
<Table.Cell>Default value is `1.25 x space unit`</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-lg</Chip>
</Table.Cell>
<Table.Cell>Default value is `2 x space unit`</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-xl</Chip>
</Table.Cell>
<Table.Cell>Default value is `3.25 x space unit`</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-xxl</Chip>
</Table.Cell>
<Table.Cell>Default value is `5.25 x space unit`</Table.Cell>
<Table.Cell>The bold font weight for the theme.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
## Spacing
We built a spacing system based on a single value `--canon-space`. This value is
used to calculate the spacing for all the components. By default if you would like to
increase or decrease the spacing between your components you can do it simply by updating
`--canon-space` and it will apply to all spacing values.
`--canon-space` is not used directly in any components but serve as an easy way to
calculate the other values.
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space</Chip>
</Table.Cell>
<Table.Cell>
The base unit for the spacing system. Default value is `0.25rem`.
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
Below is the list of all spacing values you can use in your application. We use these
tokens for pretty much each spacing properties like padding, margin, gaps, ...
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-0_5</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 0.5.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-1</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`).</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-1_5</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 1.5.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-2</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 2.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-3</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 3.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-4</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 4.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-5</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 5.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-6</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 6.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-7</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 7.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-8</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 8.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-9</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 9.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-10</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 10.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-11</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 11.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-12</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 12.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-13</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 13.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-space-14</Chip>
</Table.Cell>
<Table.Cell>Base unit (`--canon-space`) times 14.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Radius (Work in progress)
Our radius system is a little bit different from spacing as we use a factor to calculate the radius values.
Under the hood different components uses different radius values. For example, button can be set to have full radius
while a checkbox will have to remain a square.
Here are the different radius factor we recommend to use
- None: `0`
- Small: `0.75`
- Medium: `1`
- Large: `1.5`
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-factor</Chip>
</Table.Cell>
<Table.Cell>By default the value is set to medium `1`.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
+1 -1
View File
@@ -2,7 +2,7 @@ body {
display: flex;
flex-direction: row;
background-color: var(--canon-bg);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
transition: background-color 0.2s ease-in-out;
--docs-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+1 -1
View File
@@ -17,7 +17,7 @@
}
.page p {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
font-size: 1rem;
line-height: 1.5rem;
margin-top: 0;
@@ -18,7 +18,7 @@
border-bottom: 1px solid var(--canon-border);
padding: 12px 20px;
font-size: 0.875rem;
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
}
.code {
@@ -66,7 +66,7 @@
padding: 0 8px;
color: #fff;
background-color: var(--canon-bg);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
transition: background-color 0.2s ease-in-out;
border-radius: 0.25rem;
cursor: pointer;
@@ -10,7 +10,7 @@
}
.icon path {
fill: var(--canon-text-primary);
fill: var(--canon-fg-text-primary);
}
.content {
@@ -37,6 +37,6 @@
border-radius: 100px;
margin-top: var(--canon-space-3);
font-size: var(--canon-font-size-3);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
gap: var(--canon-space-2);
}
@@ -10,7 +10,7 @@
left: 0;
width: 300px;
height: 100vh;
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
background-color: var(--canon-bg-elevated);
border-right: 1px solid var(--canon-border);
padding-left: 20px;
@@ -26,7 +26,7 @@
}
.logo path {
fill: var(--canon-text-primary);
fill: var(--canon-fg-text-primary);
}
.menu {
@@ -75,11 +75,11 @@
font-family: var(--docs-font);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
.lineStatus {
font-family: var(--docs-font);
font-size: var(--canon-font-size-3);
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
}
@@ -38,7 +38,7 @@
transition: background-color 0.2s ease-in-out;
& p {
margin: 0;
margin: 0 !important;
}
}
+2 -2
View File
@@ -34,8 +34,8 @@ export const Tab = (props: React.ComponentProps<typeof TabsPrimitive.Tab>) => (
{...rest}
style={{
color: state.selected
? 'var(--canon-text-primary)'
: 'var(--canon-text-secondary)',
? 'var(--canon-fg-text-primary)'
: 'var(--canon-fg-text-secondary)',
}}
>
{children}
@@ -22,6 +22,6 @@
left: var(--active-tab-left);
width: var(--active-tab-width);
height: 1px;
background-color: var(--canon-text-primary);
background-color: var(--canon-fg-text-primary);
transition: all 0.2s ease-in-out;
}
@@ -27,21 +27,21 @@
all: unset;
height: 60px;
font-family: var(--docs-font);
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-bold);
cursor: pointer;
transition: color 0.2s ease-in-out;
&:hover {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
&[data-selected] {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
& p {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
}
@@ -60,7 +60,7 @@
}
.tab p {
color: var(--canon-text-secondary) !important;
color: var(--canon-fg-text-secondary) !important;
}
.indicator {
@@ -72,7 +72,7 @@
width: var(--active-tab-width);
height: 1px;
border-radius: 0.25rem;
background-color: var(--canon-text-primary);
background-color: var(--canon-fg-text-primary);
transition-property: translate, width, background-color;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
@@ -29,23 +29,23 @@
outline: 0;
background: none;
appearance: none;
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
user-select: none;
height: 2rem;
flex: 1;
cursor: pointer;
&[data-selected] {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
& p {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
}
@media (hover: hover) {
&:hover {
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
}
@@ -64,7 +64,7 @@
}
.tab p {
color: var(--canon-text-secondary) !important;
color: var(--canon-fg-text-secondary) !important;
}
.indicator {
+5 -2
View File
@@ -62,10 +62,13 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
</p>
),
a: ({ children, href }) => (
<a href={href} style={{ color: 'var(--canon-text-primary)' }}>
<a href={href} style={{ color: 'var(--canon-fg-text-primary)' }}>
{children as ReactNode}
</a>
),
li: ({ children }) => (
<li style={{ marginBottom: '0.5rem' }}>{children as ReactNode}</li>
),
pre: ({ children }) => {
const codeContent = React.isValidElement(children)
? (children.props as { children: string }).children
@@ -80,7 +83,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
backgroundColor: 'var(--canon-bg-elevated)',
padding: '0.2rem 0.375rem',
borderRadius: '0.25rem',
color: 'var(--canon-text-secondary)',
color: 'var(--canon-fg-text-secondary)',
border: '1px solid var(--canon-border)',
fontSize: '0.875rem',
}}
+31
View File
@@ -1,5 +1,36 @@
// Sometimes codes are not formatted correctly in the docs, so we need to use snippets
export const customTheme = `:root {
--canon-font-regular: system-ui;
--canon-font-weight-regular: 400;
--canon-font-weight-bold: 600;
--canon-bg: #f8f8f8;
--canon-bg-elevated: #fff;
/* ... other CSS variables */
/* Add your custom components styles here */
.canon-Button {
background-color: #000;
color: #fff;
}
}
[data-theme='dark'] {
--canon-font-regular: system-ui;
--canon-font-weight-regular: 400;
--canon-font-weight-bold: 600;
--canon-bg: #f8f8f8;
--canon-bg-elevated: #fff;
/* ... other CSS variables */
/* Add your custom components styles here */
.canon-Button {
background-color: #000;
color: #fff;
}
}
`;
export const grid = `import { Grid } from '@backstage/canon';
<Grid>
+5 -2
View File
@@ -62,8 +62,11 @@ export const ButtonDisabled = () => {
};
export const ButtonResponsive = () => {
// TODO: Add responsive button
return null;
return (
<Button variant={{ initial: 'primary', lg: 'secondary' }}>
Responsive Button
</Button>
);
};
export const ButtonPlayground = () => {
+1 -2
View File
@@ -19,10 +19,9 @@ export const HeadingAllVariants = () => {
};
export const HeadingResponsive = () => {
return null;
return (
<Stack>
<Heading variant={{ xs: 'title4', md: 'title2' }}>
<Heading variant={{ initial: 'title2', lg: 'title1' }}>
Responsive heading
</Heading>
</Stack>
+3 -2
View File
@@ -57,8 +57,9 @@ export const TextAllWeights = () => {
};
export const TextResponsive = () => {
// TODO: Add responsive text
return null;
return (
<Text variant={{ initial: 'body', lg: 'subtitle' }}>Responsive text</Text>
);
};
export const TextPlayground = () => {
+9 -9
View File
@@ -925,11 +925,11 @@ __metadata:
linkType: hard
"@types/node@npm:^20":
version: 20.17.12
resolution: "@types/node@npm:20.17.12"
version: 20.17.16
resolution: "@types/node@npm:20.17.16"
dependencies:
undici-types: ~6.19.2
checksum: 0c0dbeb4e1480a23071ec38e97bb3d776e0ae9828174bf45f9edcf277caa955945cb10d31d84f7a73c66aaf92ae2e022be6331bd00a4bed1f2ad9639a411d17e
checksum: dd19c04c592b602f20448187b4b6ab0293f22173a8bbe53aa3b559188f078b5261d748a107b84ee0580e2074cda760049b13d6e210f9850039bead01cb666a3f
languageName: node
linkType: hard
@@ -5368,22 +5368,22 @@ __metadata:
linkType: hard
"typescript@npm:^5":
version: 5.7.2
resolution: "typescript@npm:5.7.2"
version: 5.7.3
resolution: "typescript@npm:5.7.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: b55300c4cefee8ee380d14fa9359ccb41ff8b54c719f6bc49b424899d662a5ce62ece390ce769568c7f4d14af844085255e63788740084444eb12ef423b13433
checksum: 6c38b1e989918e576f0307e6ee013522ea480dfce5f3ca85c9b2d8adb1edeffd37f4f30cd68de0c38a44563d12ba922bdb7e36aa2dac9c51de5d561e6e9a2e9c
languageName: node
linkType: hard
"typescript@patch:typescript@^5#~builtin<compat/typescript>":
version: 5.7.2
resolution: "typescript@patch:typescript@npm%3A5.7.2#~builtin<compat/typescript>::version=5.7.2&hash=5adc0c"
version: 5.7.3
resolution: "typescript@patch:typescript@npm%3A5.7.3#~builtin<compat/typescript>::version=5.7.3&hash=5adc0c"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 803430c6da2ba73c25a21880d8d4f08a56d9d2444e6db2ea949ac4abceeece8e4a442b7b9b585db7d8a0b47ebda2060e45fe8ee8b8aca23e27ec1d4844987ee6
checksum: 633cd749d6cd7bc842c6b6245847173bba99742a60776fae3c0fbcc0d1733cd51a733995e5f4dadd8afb0e64e57d3c7dbbeae953a072ee303940eca69e22f311
languageName: node
linkType: hard
+1
View File
@@ -515,6 +515,7 @@ The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/doc
- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-modules` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`.
- Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not.
- When running tests with coverage enabled the default `babel` coverage provider can mess with the hoisting of named exports. This can be worked around by using the `v8` provider instead by setting `"coverageProvider": "v8"` in the Jest configuration, although note that the `v8` provider is a fair bit slower than the `babel` one.
- Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files.
- Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object.
@@ -34,7 +34,7 @@ import type {
} from '../../../backend-plugin-api/src/wiring/types';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import type { InternalServiceFactory } from '../../../backend-plugin-api/src/services/system/types';
import { ForwardedError, ConflictError } from '@backstage/errors';
import { ForwardedError, ConflictError, assertError } from '@backstage/errors';
import {
instanceMetadataServiceRef,
featureDiscoveryServiceRef,
@@ -405,8 +405,9 @@ export class BackendInitializer {
// Once the plugin and all modules have been initialized, we can signal that the plugin has stared up successfully
const lifecycleService = await this.#getPluginLifecycleImpl(pluginId);
await lifecycleService.startup();
} catch (error) {
initLogger.onPluginFailed(pluginId);
} catch (error: unknown) {
assertError(error);
initLogger.onPluginFailed(pluginId, error);
throw error;
}
}),
@@ -27,7 +27,7 @@ export function createInitializationLogger(
rootLogger?: RootLoggerService,
): {
onPluginStarted(pluginId: string): void;
onPluginFailed(pluginId: string): void;
onPluginFailed(pluginId: string, error: Error): void;
onAllStarted(): void;
} {
const logger = rootLogger?.child({ type: 'initialization' });
@@ -68,14 +68,15 @@ export function createInitializationLogger(
starting.delete(pluginId);
started.add(pluginId);
},
onPluginFailed(pluginId: string) {
onPluginFailed(pluginId: string, error: Error) {
starting.delete(pluginId);
const status =
starting.size > 0
? `, waiting for ${starting.size} other plugins to finish before shutting down the process`
: '';
logger?.error(
`Plugin '${pluginId}' threw an error during startup${status}`,
`Plugin '${pluginId}' threw an error during startup${status}.`,
error,
);
},
onAllStarted() {
@@ -60,9 +60,9 @@
--canon-bg: #f4f4f4;
/* Text colors */
--canon-text-primary: #000;
--canon-fg-text-primary: #000;
--canon-fg-accent: #fff;
--canon-text-secondary: #646464;
--canon-fg-text-secondary: #646464;
}
[data-theme-name='legacy'][data-theme='dark'] {
@@ -82,7 +82,7 @@
--canon-fg-danger: #f50000;
/* Text colors */
--canon-text-primary: #fff;
--canon-fg-text-primary: #fff;
--canon-fg-accent: #000;
--canon-text-secondary: #b3b3b3;
--canon-fg-text-secondary: #b3b3b3;
}
@@ -383,7 +383,7 @@ export const Border: Story = {
args: {
style: {
background: 'var(--canon-bg-elevated)',
color: 'var(--canon-text-primary)',
color: 'var(--canon-fg-text-primary)',
padding: '4px 8px',
width: '80px',
height: '32px',
+1 -1
View File
@@ -1,4 +1,4 @@
.canon-Box {
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
@@ -36,14 +36,14 @@
&:hover {
background-color: transparent;
box-shadow: inset 0 0 0 1px var(--canon-border-focus);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
}
}
.canon-Button--variant-secondary {
background-color: transparent;
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
&:hover {
box-shadow: inset 0 0 0 1px var(--canon-border-hover);
@@ -52,10 +52,10 @@
.canon-Button--variant-tertiary {
background-color: transparent;
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
&:hover {
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
}
}
@@ -23,7 +23,7 @@
gap: var(--canon-space-2);
font-size: var(--canon-font-size-xs);
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
user-select: none;
&:hover {
@@ -24,14 +24,14 @@
.canon-FieldLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-FieldDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
margin: 0;
padding-top: var(--canon-space-1_5);
}
@@ -47,7 +47,7 @@
.canon-FieldValidity {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
margin: 0;
padding-top: var(--canon-space-1_5);
}
@@ -16,7 +16,7 @@
.canon-Heading {
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
line-height: 100%;
padding: 0;
margin: 0;
@@ -21,13 +21,13 @@
background-color: var(--canon-bg-elevated);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
width: 100%;
}
.canon-Input::placeholder {
color: var(--canon-text-secondary);
color: var(--canon-fg-text-secondary);
}
.canon-Input:hover {
@@ -16,7 +16,7 @@
.canon-Text {
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
color: var(--canon-fg-text-primary);
padding: 0;
margin: 0;
}
+12 -11
View File
@@ -75,7 +75,7 @@
--canon-bg: #f8f8f8;
--canon-bg-elevated: #fff;
--canon-bg-accent: #000;
--canon-bg-accent-active: #000;
--canon-bg-accent-focus: #000;
--canon-bg-accent-disabled: #000;
--canon-bg-accent-hover: #000;
--canon-bg-tint: #edf2fe;
@@ -95,14 +95,15 @@
--canon-border-hover: rgba(0, 0, 0, 0.2);
--canon-border-success: #008000;
--canon-border-warning: #e36d05;
--canon-fg-text-primary: #000;
--canon-fg-text-secondary: #646464;
--canon-fg-link: #fff;
--canon-fg-link-hover: #fff;
--canon-fg-accent: #fff;
--canon-fg-tint: #000;
--canon-fg-danger: #e22b2b;
--canon-fg-success: #008000;
--canon-fg-warning: #e36d05;
--canon-text-link: #fff;
--canon-text-link-hover: #fff;
--canon-text-primary: #000;
--canon-text-secondary: #646464;
--canon-fg-success: #008000;
}
/* Dark theme tokens */
@@ -110,7 +111,7 @@
--canon-bg: #f8f8f8;
--canon-bg-elevated: #fff;
--canon-bg-accent: #000;
--canon-bg-accent-active: #000;
--canon-bg-accent-focus: #000;
--canon-bg-accent-disabled: #000;
--canon-bg-accent-hover: #000;
--canon-bg-tint: #edf2fe;
@@ -134,8 +135,8 @@
--canon-fg-danger: #e22b2b;
--canon-fg-success: #008000;
--canon-fg-warning: #e36d05;
--canon-text-link: #fff;
--canon-text-link-hover: #fff;
--canon-text-primary: #000;
--canon-text-secondary: #646464;
--canon-fg-link: #fff;
--canon-fg-link-hover: #fff;
--canon-fg-text-primary: #000;
--canon-fg-text-secondary: #646464;
}
+1 -1
View File
@@ -154,7 +154,7 @@ export const transforms = (options: TransformOptions): Transforms => {
],
type: 'asset/resource',
generator: {
filename: 'static/[name].[hash:8].[ext]',
filename: 'static/[name].[hash:8][ext]',
},
},
{
+1
View File
@@ -73,6 +73,7 @@
"d3-shape": "^3.0.0",
"d3-zoom": "^3.0.0",
"dagre": "^0.8.5",
"js-yaml": "^4.1.0",
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
"lodash": "^4.17.21",
+1
View File
@@ -1307,6 +1307,7 @@ export interface StructuredMetadataTableProps {
// (undocumented)
options?: {
titleFormat?: (key: string) => string;
nestedValuesAsYaml?: boolean;
};
}
@@ -18,12 +18,12 @@ import Box from '@material-ui/core/Box';
import IconButton from '@material-ui/core/IconButton';
import CopyIcon from '@material-ui/icons/FileCopy';
import classnames from 'classnames';
import React, { useEffect, useMemo, useRef } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import AutoSizer from 'react-virtualized-auto-sizer';
import { FixedSizeList } from 'react-window';
import { AnsiProcessor } from './AnsiProcessor';
import { AnsiLine, AnsiProcessor } from './AnsiProcessor';
import { LogLine } from './LogLine';
import { LogViewerControls } from './LogViewerControls';
import { HEADER_SIZE, useStyles } from './styles';
@@ -37,7 +37,9 @@ export interface RealLogViewerProps {
export function RealLogViewer(props: RealLogViewerProps) {
const classes = useStyles({ classes: props.classes });
const listRef = useRef<FixedSizeList | null>(null);
const [fixedListInstance, setFixedListInstance] = useState<FixedSizeList<
AnsiLine[]
> | null>(null);
// The processor keeps state that optimizes appending to the text
const processor = useMemo(() => new AnsiProcessor(), []);
@@ -48,10 +50,21 @@ export function RealLogViewer(props: RealLogViewerProps) {
const location = useLocation();
useEffect(() => {
if (search.resultLine !== undefined && listRef.current) {
listRef.current.scrollToItem(search.resultLine - 1, 'center');
if (fixedListInstance) {
fixedListInstance.scrollToItem(lines.length - 1, 'end');
}
}, [search.resultLine]);
}, [fixedListInstance, lines]);
useEffect(() => {
if (!fixedListInstance) {
return;
}
if (search.resultLine) {
fixedListInstance.scrollToItem(search.resultLine - 1, 'center');
} else {
fixedListInstance.scrollToItem(lines.length - 1, 'end');
}
}, [fixedListInstance, search.resultLine, lines]);
useEffect(() => {
if (location.hash) {
@@ -76,7 +89,9 @@ export function RealLogViewer(props: RealLogViewerProps) {
<LogViewerControls {...search} />
</Box>
<FixedSizeList
ref={listRef}
ref={(instance: FixedSizeList<AnsiLine[]>) => {
setFixedListInstance(instance);
}}
className={classes.log}
height={(height || 480) - HEADER_SIZE}
width={width || 640}
@@ -66,7 +66,7 @@ describe('<StructuredMetadataTable />', () => {
expect(getByText(startCase(value))).toBeInTheDocument();
});
metadata.arrayField.forEach(value => {
expect(getByText(value)).toBeInTheDocument();
expect(getByText(new RegExp(value))).toBeInTheDocument();
});
});
@@ -124,12 +124,19 @@ describe('<StructuredMetadataTable />', () => {
};
it('should make keys human readable', async () => {
const rendered = render(<StructuredMetadataTable metadata={metadata} />);
const rendered = render(
<StructuredMetadataTable
metadata={metadata}
options={{ nestedValuesAsYaml: true }}
/>,
);
expect(rendered.queryByText(/^Test A/)).toBeInTheDocument();
expect(rendered.queryByText(/^Test B/)).toBeInTheDocument();
expect(rendered.queryByText(/^Test C/)).toBeInTheDocument();
expect(rendered.queryByText(/^Test D/)).toBeInTheDocument();
expect(rendered.queryByText(/^Test E/)).toBeInTheDocument();
// nested content is displayed as yaml, so not affected by formatting
expect(rendered.queryByText(/^testC/)).toBeInTheDocument();
expect(rendered.queryByText(/testE: stuff/)).toBeInTheDocument();
});
it('should be possible to disable it', async () => {
@@ -161,14 +168,16 @@ describe('<StructuredMetadataTable />', () => {
const rendered = render(
<StructuredMetadataTable
metadata={metadata}
options={{ titleFormat: spongeBobCase }}
options={{ titleFormat: spongeBobCase, nestedValuesAsYaml: true }}
/>,
);
expect(rendered.queryByText(/^tEsTa/)).toBeInTheDocument();
expect(rendered.queryByText(/^tEsTb/)).toBeInTheDocument();
expect(rendered.queryByText(/^tEsTc/)).toBeInTheDocument();
expect(rendered.queryByText(/^tEsTd/)).toBeInTheDocument();
expect(rendered.queryByText(/^tEsTe/)).toBeInTheDocument();
// nested content is displayed as yaml, so not affected by formatting
expect(rendered.queryByText(/^testC/)).toBeInTheDocument();
expect(rendered.queryByText(/^testE/)).toBeInTheDocument();
});
});
});
@@ -15,21 +15,23 @@
*/
import React, { Fragment, ReactElement } from 'react';
import {
withStyles,
createStyles,
WithStyles,
Theme,
} from '@material-ui/core/styles';
import startCase from 'lodash/startCase';
import Typography from '@material-ui/core/Typography';
import {
MetadataTable,
MetadataTableItem,
MetadataList,
MetadataListItem,
MetadataTable,
MetadataTableItem,
} from './MetadataTable';
import { CodeSnippet } from '../CodeSnippet';
import jsyaml from 'js-yaml';
import {
Theme,
createStyles,
WithStyles,
withStyles,
} from '@material-ui/core/styles';
export type StructuredMetadataTableListClassKey = 'root';
@@ -41,7 +43,6 @@ const listStyle = createStyles({
});
export type StructuredMetadataTableNestedListClassKey = 'root';
const nestedListStyle = (theme: Theme) =>
createStyles({
root: {
@@ -110,9 +111,24 @@ function toValue(
if (React.isValidElement(value)) {
return <Fragment>{value}</Fragment>;
}
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
return renderMap(value, options, nested);
if (value !== null && typeof value === 'object') {
if (options.nestedValuesAsYaml) {
return (
<CodeSnippet
language="yaml"
text={jsyaml.dump(value)}
customStyle={{
background: 'transparent',
lineHeight: '1.4',
padding: '0',
margin: 0,
}}
/>
);
}
if (!Array.isArray(value)) {
return renderMap(value, options, nested);
}
}
if (Array.isArray(value)) {
@@ -122,7 +138,6 @@ function toValue(
if (typeof value === 'boolean') {
return <Fragment>{value ? '✅' : '❌'}</Fragment>;
}
return (
<Typography variant="body2" component="span">
{value}
@@ -167,6 +182,7 @@ export interface StructuredMetadataTableProps {
* @returns Formatted key
*/
titleFormat?: (key: string) => string;
nestedValuesAsYaml?: boolean;
};
}
@@ -174,9 +190,10 @@ type Options = Required<NonNullable<StructuredMetadataTableProps['options']>>;
/** @public */
export function StructuredMetadataTable(props: StructuredMetadataTableProps) {
const { metadata, dense = true, options = {} } = props;
const { metadata, dense = true, options } = props;
const metadataItems = mapToItems(metadata, {
titleFormat: startCase,
nestedValuesAsYaml: options?.nestedValuesAsYaml ?? false,
...options,
});
return <MetadataTable dense={dense}>{metadataItems}</MetadataTable>;
+15
View File
@@ -41,3 +41,18 @@
version "22.2.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e"
integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==
"@types/node@*":
version "22.10.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.8.tgz#e7e2602c83d27d483c056302d76b86321c4e8697"
integrity sha512-rk+QvAEGsbX/ZPiiyel6hJHNUS9cnSbPWVaZLvE+Er3tLqQFzWMz9JOfWW7XUmKvRPfxJfbl3qYWve+RGXncFw==
"@types/node@>=13.7.0":
version "22.10.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.8.tgz#e7e2602c83d27d483c056302d76b86321c4e8697"
integrity sha512-rk+QvAEGsbX/ZPiiyel6hJHNUS9cnSbPWVaZLvE+Er3tLqQFzWMz9JOfWW7XUmKvRPfxJfbl3qYWve+RGXncFw==
"@types/node@^22.0.0":
version "22.10.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.8.tgz#e7e2602c83d27d483c056302d76b86321c4e8697"
integrity sha512-rk+QvAEGsbX/ZPiiyel6hJHNUS9cnSbPWVaZLvE+Er3tLqQFzWMz9JOfWW7XUmKvRPfxJfbl3qYWve+RGXncFw==
@@ -35,6 +35,7 @@ describe('RouteRef', () => {
internal.setId('some-id');
expect(String(internal)).toBe('RouteRef{some-id}');
internal.setId('some-id'); // Should allow same ID
expect(() => internal.setId('some-other-id')).toThrow(
"RouteRef was referenced twice as both 'some-id' and 'some-other-id'",
@@ -95,7 +95,7 @@ export class RouteRefImpl implements InternalRouteRef {
if (!id) {
throw new Error(`${this.#name} id must be a non-empty string`);
}
if (this.#id) {
if (this.#id && this.#id !== id) {
throw new Error(
`${this.#name} was referenced twice as both '${this.#id}' and '${id}'`,
);
@@ -82,7 +82,12 @@ describe('generateOpenApiSchema', () => {
}),
} as any);
const { command } = await import('./index');
// Same logic as https://github.com/backstage/backstage/blob/547e41da5ac497e5a606c08c8fb57b429687d5f7/packages/repo-tools/src/commands/index.ts#L278-L297
const {
default: { command },
} = (await import('./index')) as unknown as {
default: typeof import('./index');
};
const actions = async () => {
while (!mockOn.ready) {
+1 -1
View File
@@ -437,7 +437,7 @@ export interface Config {
/**
* LdapOrg provider key
*/
ldapOrg: {
ldapOrg?: {
/**
* Id of the LdapOrg provider
*/
@@ -58,6 +58,7 @@ export const ClusterOverview = () => {
'OIDC Token Provider': value.oidcTokenProvider ?? 'N/A',
'Dashboard Link': value.dashboardUrl ?? 'N/A',
}}
options={{ nestedValuesAsYaml: true }}
/>
)}
</InfoCard>
@@ -50,7 +50,10 @@ const defaultColumns: TableColumn<INode>[] = [
<Grid container>
<Grid item xs={12}>
<Typography variant="h5">Node Info</Typography>
<StructuredMetadataTable metadata={node.status?.nodeInfo ?? {}} />
<StructuredMetadataTable
metadata={node.status?.nodeInfo ?? {}}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
<Grid item xs={12}>
<Typography variant="h5">Addresses</Typography>
@@ -61,6 +64,7 @@ const defaultColumns: TableColumn<INode>[] = [
return accum;
}, {} as any) ?? {}
}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
<Grid item xs={12}>
@@ -72,6 +76,7 @@ const defaultColumns: TableColumn<INode>[] = [
return accum;
}, {} as any) ?? {}
}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
</Grid>
@@ -84,7 +84,10 @@ const DefaultCustomResourceAccordion = ({
</AccordionSummary>
<AccordionDetails>
{Object.prototype.hasOwnProperty.call(customResource, 'status') && (
<StructuredMetadataTable metadata={customResource.status} />
<StructuredMetadataTable
metadata={customResource.status}
options={{ nestedValuesAsYaml: true }}
/>
)}
</AccordionDetails>
</Accordion>
@@ -40,13 +40,13 @@ describe('DeploymentDrawer', () => {
expect(getAllByText('Deployment')).toHaveLength(2);
expect(getByText('YAML')).toBeInTheDocument();
expect(getByText('Strategy')).toBeInTheDocument();
expect(getByText('Rolling Update:')).toBeInTheDocument();
expect(getByText(textContentMatcher('Max Surge: 25%'))).toBeInTheDocument();
expect(getByText('rollingUpdate:')).toBeInTheDocument();
expect(getByText(textContentMatcher('maxSurge: 25%'))).toBeInTheDocument();
expect(
getByText(textContentMatcher('Max Unavailable: 25%')),
getByText(textContentMatcher('maxUnavailable: 25%')),
).toBeInTheDocument();
expect(
getByText(textContentMatcher('Type: RollingUpdate')),
getByText(textContentMatcher('type: RollingUpdate')),
).toBeInTheDocument();
expect(getByText('Min Ready Seconds')).toBeInTheDocument();
expect(getByText('???')).toBeInTheDocument();
@@ -37,13 +37,13 @@ describe('IngressDrawer', () => {
expect(screen.getByText('YAML')).toBeInTheDocument();
expect(screen.getByText('Rules')).toBeInTheDocument();
expect(
screen.getByText(textContentMatcher('Host: api.awesome-host.io')),
screen.getByText(textContentMatcher('host: api.awesome-host.io')),
).toBeInTheDocument();
expect(
screen.getAllByText(textContentMatcher('Service Port: 80')),
screen.getAllByText(textContentMatcher('servicePort: 80')),
).toHaveLength(2);
expect(
screen.getAllByText(textContentMatcher('Service Name: awesome-service')),
screen.getAllByText(textContentMatcher('serviceName: awesome-service')),
).toHaveLength(2);
});
});
@@ -65,6 +65,9 @@ const IngressCard = ({ ingress }: IngressCardProps) => {
metadata={{
...ingress.spec,
}}
options={{
nestedValuesAsYaml: true,
}}
/>
);
};
@@ -237,6 +237,7 @@ const KubernetesStructuredMetadataTableDrawerContent = <
{!isYaml && (
<StructuredMetadataTable
metadata={renderObject(replaceNullsWithUndefined(object))}
options={{ nestedValuesAsYaml: true }}
/>
)}
</div>
@@ -176,6 +176,7 @@ export const ContainerCard: React.FC<ContainerCardProps> = ({
containerSpec,
containerStatus,
)}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
{containerMetrics && (
@@ -39,10 +39,10 @@ describe('ServiceDrawer', () => {
expect(screen.getByText('Cluster IP')).toBeInTheDocument();
expect(screen.getByText('Ports')).toBeInTheDocument();
expect(
screen.getByText(textContentMatcher('Target Port: 1997')),
screen.getByText(textContentMatcher('targetPort: 1997')),
).toBeInTheDocument();
expect(
screen.getByText(textContentMatcher('App: awesome-service')),
screen.getByText(textContentMatcher('app: awesome-service')),
).toBeInTheDocument();
});
});
@@ -77,6 +77,7 @@ const ServiceCard = ({ service }: ServiceCardProps) => {
ports: service.spec?.ports,
...metadata,
}}
options={{ nestedValuesAsYaml: true }}
/>
);
};
@@ -40,20 +40,20 @@ describe('StatefulSetDrawer', () => {
expect(getByText('StatefulSet')).toBeInTheDocument();
expect(getByText('YAML')).toBeInTheDocument();
expect(
getByText(textContentMatcher('Type: RollingUpdate')),
getByText(textContentMatcher('type: RollingUpdate')),
).toBeInTheDocument();
expect(getByText('Rolling Update:')).toBeInTheDocument();
expect(getByText(textContentMatcher('Max Surge: 25%'))).toBeInTheDocument();
expect(getByText('rollingUpdate:')).toBeInTheDocument();
expect(getByText(textContentMatcher('maxSurge: 25%'))).toBeInTheDocument();
expect(
getByText(textContentMatcher('Max Unavailable: 25%')),
getByText(textContentMatcher('maxUnavailable: 25%')),
).toBeInTheDocument();
expect(getByText('Pod Management Policy')).toBeInTheDocument();
expect(getByText('Parallel')).toBeInTheDocument();
expect(getByText('Service Name')).toBeInTheDocument();
expect(getByText('Selector')).toBeInTheDocument();
expect(getByText('Match Labels:')).toBeInTheDocument();
expect(getByText('matchLabels:')).toBeInTheDocument();
expect(
getByText(textContentMatcher('App: dice-roller')),
getByText(textContentMatcher('app: dice-roller')),
).toBeInTheDocument();
expect(getByText('Revision History Limit')).toBeInTheDocument();
expect(getByText('10')).toBeInTheDocument();
+39
View File
@@ -34,7 +34,10 @@ import fixture2 from '../src/__fixtures__/2-deployments.json';
import fixture3 from '../src/__fixtures__/1-cronjobs.json';
import fixture4 from '../src/__fixtures__/2-cronjobs.json';
import fixture5 from '../src/__fixtures__/1-rollouts.json';
import fixture6 from '../src/__fixtures__/3-ingresses.json';
import fixture7 from '../src/__fixtures__/2-statefulsets.json';
import { TestApiProvider } from '@backstage/test-utils';
import { StructuredMetadataTable } from '@backstage/core-components';
const mockEntity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
@@ -134,6 +137,12 @@ class MockKubernetesClient implements KubernetesApi {
}
}
const metadata = {
testA: 'stuff',
testB: { testC: 'stuff' },
testD: [{ testE: 'stuff' }],
};
createDevApp()
.addPage({
path: '/fixture-1',
@@ -200,5 +209,35 @@ createDevApp()
</TestApiProvider>
),
})
.addPage({
path: '/fixture-6',
title: 'Fixture 6',
element: (
<TestApiProvider
apis={[[kubernetesApiRef, new MockKubernetesClient(fixture6)]]}
>
<EntityProvider entity={mockEntity}>
<EntityKubernetesContent />
</EntityProvider>
</TestApiProvider>
),
})
.addPage({
path: '/fixture-7',
title: 'Fixture 7',
element: (
<TestApiProvider
apis={[[kubernetesApiRef, new MockKubernetesClient(fixture7)]]}
>
<EntityProvider entity={mockEntity}>
<StructuredMetadataTable
metadata={metadata}
options={{ nestedValuesAsYaml: true }}
/>
<EntityKubernetesContent />
</EntityProvider>
</TestApiProvider>
),
})
.registerPlugin(kubernetesPlugin)
.render();
@@ -0,0 +1,372 @@
{
"ingresses": [
{
"metadata": {
"name": "app-service-endpoint.dice-roller.backstage.io",
"namespace": "dice-roller",
"uid": "00000000-0000-0000-0000-000000000000",
"resourceVersion": "4749260703",
"generation": 1,
"creationTimestamp": "2023-08-16T14:52:16Z",
"labels": {
"backstage.io/kubernetes-id": "dice-roller",
"ingress-class": "istio-internal"
},
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "spec",
"metacontroller.k8s.io/last-applied-configuration": "spec",
"nginx.ingress.kubernetes.io/auth-signin": "https://backstage.io/authenticate",
"nginx.ingress.kubernetes.io/configuration-snippet": "config",
"nginx.ingress.kubernetes.io/service-upstream": "true",
"nginx.ingress.kubernetes.io/upstream-vhost": "app-service-endpoint.dice-roller.backstage.services"
},
"ownerReferences": [
{
"apiVersion": "networking.backstage.com/v1beta1",
"kind": "MeshIntegration",
"name": "app-service-endpoint",
"uid": "00000000-0000-0000-0000-000000000000",
"controller": true,
"blockOwnerDeletion": true
}
],
"managedFields": [
{
"manager": "nginx-ingress-controller",
"operation": "Update",
"apiVersion": "networking.k8s.io/v1",
"time": "2023-08-16T14:52:56Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:status": {
"f:loadBalancer": {
"f:ingress": {}
}
}
},
"subresource": "status"
},
{
"manager": "kubectl-client-side-apply",
"operation": "Update",
"apiVersion": "networking.k8s.io/v1",
"time": "2023-12-15T16:33:17Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:kubectl.kubernetes.io/last-applied-configuration": {},
"f:nginx.ingress.kubernetes.io/auth-signin": {},
"f:nginx.ingress.kubernetes.io/configuration-snippet": {},
"f:nginx.ingress.kubernetes.io/service-upstream": {},
"f:nginx.ingress.kubernetes.io/upstream-vhost": {}
},
"f:labels": {
".": {},
"f:backstage.io/kubernetes-id": {},
"f:ingress-class": {}
}
},
"f:spec": {
"f:ingressClassName": {},
"f:rules": {},
"f:tls": {}
}
}
},
{
"manager": "kubectl-label",
"operation": "Update",
"apiVersion": "networking.k8s.io/v1",
"time": "2024-02-21T15:00:44Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:labels": {
"f:controller-uid": {}
}
}
}
},
{
"manager": "metacontroller",
"operation": "Update",
"apiVersion": "networking.k8s.io/v1",
"time": "2024-02-21T15:01:01Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
"f:metacontroller.k8s.io/last-applied-configuration": {}
},
"f:ownerReferences": {
".": {},
"k:{\"uid\":\"00000000-0000-0000-0000-000000000000\"}": {}
}
}
}
}
]
},
"spec": {
"ingressClassName": "istio-internal",
"tls": {
"hosts": ["app-service-endpoint.dice-roller.backstage.io"],
"secretName": "dice-roller-wildcard-certificate"
},
"rules": [
{
"host": "app-service-endpoint.dice-roller.backstage.io",
"http": {
"paths": [
{
"pathType": "ImplementationSpecific",
"backend": {
"service": {
"name": "app-service-endpoint",
"port": {
"number": 8080
}
}
}
},
{
"pathType": "Second item in the list",
"backend": {
"service": {
"name": "app-service-endpoint",
"port": {
"number": 8080
}
}
}
}
]
}
}
]
},
"status": {
"loadBalancer": {
"ingress": [
{
"ip": "1.2.3.4"
}
]
}
}
}
],
"deployments": [
{
"metadata": {
"annotations": {
"deployment.kubernetes.io/revision": "2",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"replicas\":10,\"selector\":{\"matchLabels\":{\"app\":\"dice-roller\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"dice-roller\",\"backstage.io/kubernetes-id\":\"dice-roller\"}},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80}]}]}}}}\n"
},
"creationTimestamp": "2020-09-23T12:00:55.000Z",
"generation": 3,
"labels": {
"backstage.io/kubernetes-id": "dice-roller"
},
"managedFields": [
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:kubectl.kubernetes.io/last-applied-configuration": {}
},
"f:labels": {
".": {},
"f:backstage.io/kubernetes-id": {}
}
},
"f:spec": {
"f:progressDeadlineSeconds": {},
"f:replicas": {},
"f:revisionHistoryLimit": {},
"f:selector": {
"f:matchLabels": {
".": {},
"f:app": {}
}
},
"f:strategy": {
"f:rollingUpdate": {
".": {},
"f:maxSurge": {},
"f:maxUnavailable": {}
},
"f:type": {}
},
"f:template": {
"f:metadata": {
"f:labels": {
".": {},
"f:app": {},
"f:backstage.io/kubernetes-id": {}
}
},
"f:spec": {
"f:containers": {
"k:{\"name\":\"nginx\"}": {
".": {},
"f:image": {},
"f:imagePullPolicy": {},
"f:name": {},
"f:ports": {
".": {},
"k:{\"containerPort\":80,\"protocol\":\"TCP\"}": {
".": {},
"f:containerPort": {},
"f:protocol": {}
}
},
"f:resources": {},
"f:terminationMessagePath": {},
"f:terminationMessagePolicy": {}
}
},
"f:dnsPolicy": {},
"f:restartPolicy": {},
"f:schedulerName": {},
"f:securityContext": {},
"f:terminationGracePeriodSeconds": {}
}
}
}
},
"manager": "kubectl",
"operation": "Update",
"time": "2020-09-25T09:58:50.000Z"
},
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
"f:deployment.kubernetes.io/revision": {}
}
},
"f:status": {
"f:availableReplicas": {},
"f:conditions": {
".": {},
"k:{\"type\":\"Available\"}": {
".": {},
"f:lastTransitionTime": {},
"f:lastUpdateTime": {},
"f:message": {},
"f:reason": {},
"f:status": {},
"f:type": {}
},
"k:{\"type\":\"Progressing\"}": {
".": {},
"f:lastTransitionTime": {},
"f:lastUpdateTime": {},
"f:message": {},
"f:reason": {},
"f:status": {},
"f:type": {}
}
},
"f:observedGeneration": {},
"f:readyReplicas": {},
"f:replicas": {},
"f:updatedReplicas": {}
}
},
"manager": "kube-controller-manager",
"operation": "Update",
"time": "2020-09-25T09:58:55.000Z"
}
],
"name": "dice-roller",
"namespace": "default",
"resourceVersion": "593230",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/dice-roller",
"uid": "7551e949-42d1-4061-83c5-9da107186e47"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 10,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "dice-roller"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "dice-roller",
"backstage.io/kubernetes-id": "dice-roller"
}
},
"spec": {
"containers": [
{
"image": "nginx:1.14.2",
"imagePullPolicy": "IfNotPresent",
"name": "nginx",
"ports": [
{
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30
}
}
},
"status": {
"availableReplicas": 10,
"conditions": [
{
"lastTransitionTime": "2020-09-23T12:00:55.000Z",
"lastUpdateTime": "2020-09-24T11:39:28.000Z",
"message": "ReplicaSet \"dice-roller-6c8646bfd\" has successfully progressed.",
"reason": "NewReplicaSetAvailable",
"status": "True",
"type": "Progressing"
},
{
"lastTransitionTime": "2020-09-25T09:58:55.000Z",
"lastUpdateTime": "2020-09-25T09:58:55.000Z",
"message": "Deployment has minimum availability.",
"reason": "MinimumReplicasAvailable",
"status": "True",
"type": "Available"
}
],
"observedGeneration": 3,
"readyReplicas": 10,
"replicas": 10,
"updatedReplicas": 10
}
}
]
}
@@ -991,6 +991,27 @@ describe('NunjucksWorkflowRunner', () => {
);
expect(fakeActionHandler).not.toHaveBeenCalled();
});
it('should validate each parameter renders to a valid value', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
each: '${{parameters.data}}',
action: 'jest-validated-action',
input: { foo: '${{each.value}}' },
},
],
output: {},
parameters: {},
});
await expect(runner.execute(task)).rejects.toThrow(
'Invalid value on action jest-validated-action.each parameter, "${{parameters.data}}" cannot be resolved to a value',
);
expect(fakeActionHandler).not.toHaveBeenCalled();
});
});
describe('secrets', () => {
@@ -310,13 +310,21 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
return;
}
}
const resolvedEach =
step.each && this.render(step.each, context, renderTemplate);
if (step.each && !resolvedEach) {
throw new InputError(
`Invalid value on action ${action.id}.each parameter, "${step.each}" cannot be resolved to a value`,
);
}
const iterations = (
step.each
? Object.entries(this.render(step.each, context, renderTemplate)).map(
([key, value]) => ({
each: { key, value },
}),
)
resolvedEach
? Object.entries(resolvedEach).map(([key, value]) => ({
each: { key, value },
}))
: [{}]
).map(i => ({
...i,
@@ -20,7 +20,6 @@ import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import { makeStyles } from '@material-ui/core/styles';
import { ResizableBox } from 'react-resizable';
import {
ScaffolderTaskOutput,
scaffolderApiRef,
@@ -119,6 +118,7 @@ export const OngoingTask = (props: {
useEffect(() => {
if (taskStream.completed && !taskStream.error) {
setLogVisibleState(true);
setButtonBarVisibleState(false);
}
}, [taskStream.error, taskStream.completed]);
@@ -292,13 +292,11 @@ export const OngoingTask = (props: {
) : null}
{logsVisible ? (
<ResizableBox height={240} minConstraints={[0, 160]} axis="y">
<Paper style={{ height: '100%' }}>
<Box padding={2} height="100%">
<TaskLogStream logs={taskStream.stepLogs} />
</Box>
</Paper>
</ResizableBox>
<Paper style={{ height: '100%' }}>
<Box padding={2} height="100%">
<TaskLogStream logs={taskStream.stepLogs} />
</Box>
</Paper>
) : null}
</Content>
</Page>
+1
View File
@@ -170,6 +170,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({
{
defaultPath: 'docs',
defaultTitle: 'TechDocs',
routeRef: convertLegacyRouteRef(rootCatalogDocsRouteRef),
loader: () =>
import('./Router').then(({ EmbeddedDocsRouter }) =>
compatWrapper(
+43 -35
View File
@@ -4291,6 +4291,7 @@ __metadata:
d3-zoom: ^3.0.0
dagre: ^0.8.5
history: ^5.0.0
js-yaml: ^4.1.0
linkify-react: 4.1.3
linkifyjs: 4.1.3
lodash: ^4.17.21
@@ -19314,13 +19315,13 @@ __metadata:
linkType: hard
"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.29":
version: 3.3.33
resolution: "@types/dockerode@npm:3.3.33"
version: 3.3.34
resolution: "@types/dockerode@npm:3.3.34"
dependencies:
"@types/docker-modem": "*"
"@types/node": "*"
"@types/ssh2": "*"
checksum: 4e3ed41f3b89a213c9ba923862b653f54ee7232b2c95553410d0c3846396efb592d0291fa2f50d4b47d42a2ae0b49f422e72efcdd845d0ebb60da2e8990e9293
checksum: 0cb35276fc76ee8ae9d6db7054198e23370f753894d06aa56efbf232032009c6f63a3bfce9d71e6272f3240040d9da2d2a56671d24f34e598ff2000df946ecbd
languageName: node
linkType: hard
@@ -20735,11 +20736,11 @@ __metadata:
linkType: hard
"@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.3, @types/ws@npm:^8.5.4":
version: 8.5.13
resolution: "@types/ws@npm:8.5.13"
version: 8.5.14
resolution: "@types/ws@npm:8.5.14"
dependencies:
"@types/node": "*"
checksum: f17023ce7b89c6124249c90211803a4aaa02886e12bc2d0d2cd47fa665eeb058db4d871ce4397d8e423f6beea97dd56835dd3fdbb921030fe4d887601e37d609
checksum: b63d25146a0d2ebb9cb35e4b68c5a01d81b8f4657d62b0a0d9470df6a357798349a44064b2f84b8f98a553ba84d9a5ee302d3053feef02c7771010c55d290ca6
languageName: node
linkType: hard
@@ -24495,7 +24496,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^5.4.1, chalk@npm:~5.4.1":
"chalk@npm:^5.4.1":
version: 5.4.1
resolution: "chalk@npm:5.4.1"
checksum: 0c656f30b782fed4d99198825c0860158901f449a6b12b818b0aabad27ec970389e7e8767d0e00762175b23620c812e70c4fd92c0210e55fc2d993638b74e86e
@@ -25167,10 +25168,10 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:*, commander@npm:^12.0.0, commander@npm:^12.1.0, commander@npm:~12.1.0":
version: 12.1.0
resolution: "commander@npm:12.1.0"
checksum: 68e9818b00fc1ed9cdab9eb16905551c2b768a317ae69a5e3c43924c2b20ac9bb65b27e1cab36aeda7b6496376d4da908996ba2c0b5d79463e0fb1e77935d514
"commander@npm:*, commander@npm:^13.1.0":
version: 13.1.0
resolution: "commander@npm:13.1.0"
checksum: 8ca2fcb33caf2aa06fba3722d7a9440921331d54019dabf906f3603313e7bf334b009b862257b44083ff65d5a3ab19e83ad73af282bd5319f01dc228bdf87ef0
languageName: node
linkType: hard
@@ -25209,6 +25210,13 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^12.0.0, commander@npm:^12.1.0":
version: 12.1.0
resolution: "commander@npm:12.1.0"
checksum: 68e9818b00fc1ed9cdab9eb16905551c2b768a317ae69a5e3c43924c2b20ac9bb65b27e1cab36aeda7b6496376d4da908996ba2c0b5d79463e0fb1e77935d514
languageName: node
linkType: hard
"commander@npm:^2.19.0, commander@npm:^2.20.0":
version: 2.20.3
resolution: "commander@npm:2.20.3"
@@ -26459,7 +26467,7 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:~4.4.0":
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0":
version: 4.4.0
resolution: "debug@npm:4.4.0"
dependencies:
@@ -28836,7 +28844,7 @@ __metadata:
languageName: node
linkType: hard
"execa@npm:~8.0.1":
"execa@npm:^8.0.1":
version: 8.0.1
resolution: "execa@npm:8.0.1"
dependencies:
@@ -34713,7 +34721,7 @@ __metadata:
languageName: node
linkType: hard
"lilconfig@npm:~3.1.3":
"lilconfig@npm:^3.1.3":
version: 3.1.3
resolution: "lilconfig@npm:3.1.3"
checksum: 644eb10830350f9cdc88610f71a921f510574ed02424b57b0b3abb66ea725d7a082559552524a842f4e0272c196b88dfe1ff7d35ffcc6f45736777185cd67c9a
@@ -34763,22 +34771,22 @@ __metadata:
linkType: hard
"lint-staged@npm:^15.0.0":
version: 15.4.1
resolution: "lint-staged@npm:15.4.1"
version: 15.4.2
resolution: "lint-staged@npm:15.4.2"
dependencies:
chalk: ~5.4.1
commander: ~12.1.0
debug: ~4.4.0
execa: ~8.0.1
lilconfig: ~3.1.3
listr2: ~8.2.5
micromatch: ~4.0.8
pidtree: ~0.6.0
string-argv: ~0.3.2
yaml: ~2.6.1
chalk: ^5.4.1
commander: ^13.1.0
debug: ^4.4.0
execa: ^8.0.1
lilconfig: ^3.1.3
listr2: ^8.2.5
micromatch: ^4.0.8
pidtree: ^0.6.0
string-argv: ^0.3.2
yaml: ^2.7.0
bin:
lint-staged: bin/lint-staged.js
checksum: 6c17747e9379a08ad3d15aab869a362c750d357182c76ca88121f01143910a2a2d5b1673536f3ba9db2331bf2102ffee115532736dd9c3fa2696344732a47d3d
checksum: bdb031d64f388aea166b3beaaff8a0a1503e50433959805985b43fcd788c9bfaf1e3a6c0852d830a6c75345b9c0809c5e6dab80d48460e3d6317b7ca0f842df8
languageName: node
linkType: hard
@@ -34789,7 +34797,7 @@ __metadata:
languageName: node
linkType: hard
"listr2@npm:~8.2.5":
"listr2@npm:^8.2.5":
version: 8.2.5
resolution: "listr2@npm:8.2.5"
dependencies:
@@ -36266,7 +36274,7 @@ __metadata:
languageName: node
linkType: hard
"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8, micromatch@npm:~4.0.8":
"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8":
version: 4.0.8
resolution: "micromatch@npm:4.0.8"
dependencies:
@@ -39212,7 +39220,7 @@ __metadata:
languageName: node
linkType: hard
"pidtree@npm:~0.6.0":
"pidtree@npm:^0.6.0":
version: 0.6.0
resolution: "pidtree@npm:0.6.0"
bin:
@@ -43959,7 +43967,7 @@ __metadata:
languageName: node
linkType: hard
"string-argv@npm:~0.3.1, string-argv@npm:~0.3.2":
"string-argv@npm:^0.3.2, string-argv@npm:~0.3.1":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
checksum: 8703ad3f3db0b2641ed2adbb15cf24d3945070d9a751f9e74a924966db9f325ac755169007233e8985a39a6a292f14d4fee20482989b89b96e473c4221508a0f
@@ -47657,12 +47665,12 @@ __metadata:
languageName: node
linkType: hard
"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:~2.6.1":
version: 2.6.1
resolution: "yaml@npm:2.6.1"
"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:^2.7.0":
version: 2.7.0
resolution: "yaml@npm:2.7.0"
bin:
yaml: bin.mjs
checksum: 5cf2627f121dcf04ccdebce8e6cbac7c9983d465c4eab314f6fbdc13cda8a07f4e8f9c2252a382b30bcabe05ee3c683647293afd52eb37cbcefbdc7b6ebde9ee
checksum: 6e8b2f9b9d1b18b10274d58eb3a47ec223d9a93245a890dcb34d62865f7e744747190a9b9177d5f0ef4ea2e44ad2c0214993deb42e0800766203ac46f00a12dd
languageName: node
linkType: hard