@backstage/plugin-scaffolder 0.14.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/dist/esm/{Router-ba25f61c.esm.js → Router-773d053b.esm.js} +359 -47
- package/dist/esm/Router-773d053b.esm.js.map +1 -0
- package/dist/esm/{index-da137e20.esm.js → index-25fdb62e.esm.js} +39 -203
- package/dist/esm/index-25fdb62e.esm.js.map +1 -0
- package/dist/index.d.ts +137 -35
- package/dist/index.esm.js +1 -2
- package/dist/index.esm.js.map +1 -1
- package/package.json +26 -21
- package/dist/esm/Router-ba25f61c.esm.js.map +0 -1
- package/dist/esm/index-da137e20.esm.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# @backstage/plugin-scaffolder
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy).
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- 9a408928a1: **BREAKING**: Removed the unused `titleComponent` property of `groups` passed to the `ScaffolderPage`. The property was already ignored, but existing usage should migrated to use the `title` property instead, which now accepts any `ReactNode`.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 9b7e361783: Remove beta labels
|
|
16
|
+
- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3
|
|
17
|
+
- 20a262c214: The `ScaffolderPage` now uses the `CatalogFilterLayout`, which means the filters are put in a drawer on smaller screens.
|
|
18
|
+
- f24ef7864e: Minor typo fixes
|
|
19
|
+
- d8716924d6: Implement a template preview page (`/create/preview`) to test creating form UIs
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @backstage/core-components@0.9.2
|
|
22
|
+
- @backstage/core-plugin-api@1.0.0
|
|
23
|
+
- @backstage/integration-react@1.0.0
|
|
24
|
+
- @backstage/plugin-catalog-react@1.0.0
|
|
25
|
+
- @backstage/plugin-permission-react@0.3.4
|
|
26
|
+
- @backstage/catalog-model@1.0.0
|
|
27
|
+
- @backstage/plugin-scaffolder-common@1.0.0
|
|
28
|
+
- @backstage/integration@1.0.0
|
|
29
|
+
- @backstage/catalog-client@1.0.0
|
|
30
|
+
- @backstage/config@1.0.0
|
|
31
|
+
- @backstage/errors@1.0.0
|
|
32
|
+
- @backstage/types@1.0.0
|
|
33
|
+
- @backstage/plugin-catalog-common@1.0.0
|
|
34
|
+
|
|
35
|
+
## 0.15.0
|
|
36
|
+
|
|
37
|
+
### Minor Changes
|
|
38
|
+
|
|
39
|
+
- 310e905998: The following deprecations are now breaking and have been removed:
|
|
40
|
+
|
|
41
|
+
- **BREAKING**: Support for `backstage.io/v1beta2` Software Templates has been removed. Please migrate your legacy templates to the new `scaffolder.backstage.io/v1beta3` `apiVersion` by following the [migration guide](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3)
|
|
42
|
+
|
|
43
|
+
- **BREAKING**: Removed the deprecated `TemplateMetadata`. Please use `TemplateInfo` instead.
|
|
44
|
+
|
|
45
|
+
- **BREAKING**: Removed the deprecated `context.baseUrl`. It's now available on `context.templateInfo.baseUrl`.
|
|
46
|
+
|
|
47
|
+
- **BREAKING**: Removed the deprecated `DispatchResult`, use `TaskBrokerDispatchResult` instead.
|
|
48
|
+
|
|
49
|
+
- **BREAKING**: Removed the deprecated `runCommand`, use `executeShellCommond` instead.
|
|
50
|
+
|
|
51
|
+
- **BREAKING**: Removed the deprecated `Status` in favour of `TaskStatus` instead.
|
|
52
|
+
|
|
53
|
+
- **BREAKING**: Removed the deprecated `TaskState` in favour of `CurrentClaimedTask` instead.
|
|
54
|
+
|
|
55
|
+
- 1360f7d73a: **BREAKING**: Removed `ScaffolderTaskOutput.entityRef` and `ScaffolderTaskOutput.remoteUrl`, which both have been deprecated for over a year. Please use the `links` output instead.
|
|
56
|
+
- e63e5a9452: Removed the following previously deprecated exports:
|
|
57
|
+
|
|
58
|
+
- **BREAKING**: Removed the deprecated `TemplateList` component and the `TemplateListProps` type. Please use the `TemplateCard` to create your own list component instead to render these lists.
|
|
59
|
+
|
|
60
|
+
- **BREAKING**: Removed the deprecated `setSecret` method, please use `setSecrets` instead.
|
|
61
|
+
|
|
62
|
+
- **BREAKING**: Removed the deprecated `TemplateCardComponent` and `TaskPageComponent` props from the `ScaffolderPage` component. These are now provided using the `components` prop with the shape `{{ TemplateCardComponent: () => JSX.Element, TaskPageComponent: () => JSX.Element }}`
|
|
63
|
+
|
|
64
|
+
- **BREAKING**: Removed `JobStatus` as this type was actually a legacy type used in `v1alpha` templates and the workflow engine and should no longer be used or depended on.
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- d741c97b98: Render markdown for description in software templates
|
|
69
|
+
- 33e58456b5: Fixing the border color for the `FavoriteEntity` star button on the `TemplateCard`
|
|
70
|
+
- Updated dependencies
|
|
71
|
+
- @backstage/plugin-catalog-react@0.9.0
|
|
72
|
+
- @backstage/core-components@0.9.1
|
|
73
|
+
- @backstage/plugin-scaffolder-common@0.3.0
|
|
74
|
+
- @backstage/catalog-model@0.13.0
|
|
75
|
+
- @backstage/plugin-catalog-common@0.2.2
|
|
76
|
+
- @backstage/catalog-client@0.9.0
|
|
77
|
+
- @backstage/integration-react@0.1.25
|
|
78
|
+
|
|
79
|
+
## 0.15.0-next.0
|
|
80
|
+
|
|
81
|
+
### Minor Changes
|
|
82
|
+
|
|
83
|
+
- 310e905998: The following deprecations are now breaking and have been removed:
|
|
84
|
+
|
|
85
|
+
- **BREAKING**: Support for `backstage.io/v1beta2` Software Templates has been removed. Please migrate your legacy templates to the new `scaffolder.backstage.io/v1beta3` `apiVersion` by following the [migration guide](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3)
|
|
86
|
+
|
|
87
|
+
- **BREAKING**: Removed the deprecated `TemplateMetadata`. Please use `TemplateInfo` instead.
|
|
88
|
+
|
|
89
|
+
- **BREAKING**: Removed the deprecated `context.baseUrl`. It's now available on `context.templateInfo.baseUrl`.
|
|
90
|
+
|
|
91
|
+
- **BREAKING**: Removed the deprecated `DispatchResult`, use `TaskBrokerDispatchResult` instead.
|
|
92
|
+
|
|
93
|
+
- **BREAKING**: Removed the deprecated `runCommand`, use `executeShellCommond` instead.
|
|
94
|
+
|
|
95
|
+
- **BREAKING**: Removed the deprecated `Status` in favour of `TaskStatus` instead.
|
|
96
|
+
|
|
97
|
+
- **BREAKING**: Removed the deprecated `TaskState` in favour of `CurrentClaimedTask` instead.
|
|
98
|
+
|
|
99
|
+
- 1360f7d73a: **BREAKING**: Removed `ScaffolderTaskOutput.entityRef` and `ScaffolderTaskOutput.remoteUrl`, which both have been deprecated for over a year. Please use the `links` output instead.
|
|
100
|
+
- e63e5a9452: Removed the following previously deprecated exports:
|
|
101
|
+
|
|
102
|
+
- **BREAKING**: Removed the deprecated `TemplateList` component and the `TemplateListProps` type. Please use the `TemplateCard` to create your own list component instead to render these lists.
|
|
103
|
+
|
|
104
|
+
- **BREAKING**: Removed the deprecated `setSecret` method, please use `setSecrets` instead.
|
|
105
|
+
|
|
106
|
+
- **BREAKING**: Removed the deprecated `TemplateCardComponent` and `TaskPageComponent` props from the `ScaffolderPage` component. These are now provided using the `components` prop with the shape `{{ TemplateCardComponent: () => JSX.Element, TaskPageComponent: () => JSX.Element }}`
|
|
107
|
+
|
|
108
|
+
- **BREAKING**: Removed `JobStatus` as this type was actually a legacy type used in `v1alpha` templates and the workflow engine and should no longer be used or depended on.
|
|
109
|
+
|
|
110
|
+
### Patch Changes
|
|
111
|
+
|
|
112
|
+
- d741c97b98: Render markdown for description in software templates
|
|
113
|
+
- Updated dependencies
|
|
114
|
+
- @backstage/plugin-catalog-react@0.9.0-next.0
|
|
115
|
+
- @backstage/core-components@0.9.1-next.0
|
|
116
|
+
- @backstage/plugin-scaffolder-common@0.3.0-next.0
|
|
117
|
+
- @backstage/catalog-model@0.13.0-next.0
|
|
118
|
+
- @backstage/plugin-catalog-common@0.2.2-next.0
|
|
119
|
+
- @backstage/catalog-client@0.9.0-next.0
|
|
120
|
+
- @backstage/integration-react@0.1.25-next.0
|
|
121
|
+
|
|
3
122
|
## 0.14.0
|
|
4
123
|
|
|
5
124
|
### Minor Changes
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React, { useState, useContext, useCallback } from 'react';
|
|
2
2
|
import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router';
|
|
3
|
-
import {
|
|
4
|
-
import { useRouteRef, useApi, errorApiRef, featureFlagsApiRef, useApiHolder, useElementFilter } from '@backstage/core-plugin-api';
|
|
5
|
-
import { EntityListProvider, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker } from '@backstage/plugin-catalog-react';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { ItemCardHeader, MarkdownContent, Button, ContentHeader, Progress, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage } from '@backstage/core-components';
|
|
4
|
+
import { useRouteRef, useApi, errorApiRef, featureFlagsApiRef, useApiHolder, alertApiRef, useElementFilter } from '@backstage/core-plugin-api';
|
|
5
|
+
import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
|
|
6
|
+
import { E as EntityPicker, a as EntityNamePicker, e as entityNamePickerValidation, b as EntityTagsPicker, R as RepoUrlPicker, r as repoPickerValidation, O as OwnerPicker, c as OwnedEntityPicker, s as selectedTemplateRouteRef, d as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, f as scaffolderApiRef, g as scaffolderTaskRouteRef, h as rootRouteRef, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, j as SecretsContextProvider, k as TaskPage } from './index-25fdb62e.esm.js';
|
|
7
|
+
import { RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
|
|
8
|
+
import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, IconButton, Tooltip, Link, Stepper, Step, StepLabel, StepContent, Button as Button$1, Paper, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Grid, FormControl, InputLabel, Select, MenuItem } from '@material-ui/core';
|
|
9
|
+
import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
|
|
10
|
+
import WarningIcon from '@material-ui/icons/Warning';
|
|
8
11
|
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
|
|
9
12
|
import { usePermission } from '@backstage/plugin-permission-react';
|
|
10
13
|
import qs from 'qs';
|
|
@@ -13,20 +16,22 @@ import useAsync from 'react-use/lib/useAsync';
|
|
|
13
16
|
import { withTheme } from '@rjsf/core';
|
|
14
17
|
import { Theme } from '@rjsf/material-ui';
|
|
15
18
|
import cloneDeep from 'lodash/cloneDeep';
|
|
16
|
-
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
17
19
|
import classNames from 'classnames';
|
|
20
|
+
import useDebounce from 'react-use/lib/useDebounce';
|
|
21
|
+
import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
|
|
22
|
+
import { showPanel } from '@codemirror/panel';
|
|
23
|
+
import { StreamLanguage } from '@codemirror/stream-parser';
|
|
24
|
+
import CodeMirror from '@uiw/react-codemirror';
|
|
25
|
+
import yaml from 'yaml';
|
|
18
26
|
import '@backstage/errors';
|
|
19
27
|
import 'zen-observable';
|
|
20
28
|
import '@material-ui/core/FormControl';
|
|
21
29
|
import '@material-ui/lab/Autocomplete';
|
|
22
30
|
import 'react-use/lib/useEffectOnce';
|
|
23
31
|
import '@material-ui/lab';
|
|
24
|
-
import '@backstage/integration-react';
|
|
25
32
|
import '@material-ui/core/FormHelperText';
|
|
26
33
|
import '@material-ui/core/Input';
|
|
27
34
|
import '@material-ui/core/InputLabel';
|
|
28
|
-
import 'react-use/lib/useDebounce';
|
|
29
|
-
import '@material-ui/icons/Warning';
|
|
30
35
|
import 'lodash/capitalize';
|
|
31
36
|
import '@material-ui/icons/CheckBox';
|
|
32
37
|
import '@material-ui/icons/CheckBoxOutlineBlank';
|
|
@@ -74,19 +79,174 @@ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
|
|
|
74
79
|
}
|
|
75
80
|
];
|
|
76
81
|
|
|
77
|
-
const useStyles$
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
const useStyles$2 = makeStyles((theme) => ({
|
|
83
|
+
cardHeader: {
|
|
84
|
+
position: "relative"
|
|
85
|
+
},
|
|
86
|
+
title: {
|
|
87
|
+
backgroundImage: ({ backgroundImage }) => backgroundImage
|
|
88
|
+
},
|
|
89
|
+
box: {
|
|
90
|
+
overflow: "hidden",
|
|
91
|
+
textOverflow: "ellipsis",
|
|
92
|
+
display: "-webkit-box",
|
|
93
|
+
"-webkit-line-clamp": 10,
|
|
94
|
+
"-webkit-box-orient": "vertical",
|
|
95
|
+
paddingBottom: "0.8em"
|
|
96
|
+
},
|
|
97
|
+
label: {
|
|
98
|
+
color: theme.palette.text.secondary,
|
|
99
|
+
textTransform: "uppercase",
|
|
100
|
+
fontSize: "0.65rem",
|
|
101
|
+
fontWeight: "bold",
|
|
102
|
+
letterSpacing: 0.5,
|
|
103
|
+
lineHeight: 1,
|
|
104
|
+
paddingBottom: "0.2rem"
|
|
105
|
+
},
|
|
106
|
+
leftButton: {
|
|
107
|
+
marginRight: "auto"
|
|
108
|
+
},
|
|
109
|
+
starButton: {
|
|
110
|
+
position: "absolute",
|
|
111
|
+
top: theme.spacing(0.5),
|
|
112
|
+
right: theme.spacing(0.5),
|
|
113
|
+
padding: "0.25rem",
|
|
114
|
+
color: "#fff"
|
|
115
|
+
}
|
|
116
|
+
}));
|
|
117
|
+
const useDeprecationStyles = makeStyles((theme) => ({
|
|
118
|
+
deprecationIcon: {
|
|
119
|
+
position: "absolute",
|
|
120
|
+
top: theme.spacing(0.5),
|
|
121
|
+
right: theme.spacing(3.5),
|
|
122
|
+
padding: "0.25rem"
|
|
123
|
+
},
|
|
124
|
+
link: {
|
|
125
|
+
color: theme.palette.warning.light
|
|
83
126
|
}
|
|
84
127
|
}));
|
|
128
|
+
const getTemplateCardProps = (template) => {
|
|
129
|
+
var _a, _b, _c, _d, _e;
|
|
130
|
+
return {
|
|
131
|
+
key: template.metadata.uid,
|
|
132
|
+
name: template.metadata.name,
|
|
133
|
+
title: `${(_a = template.metadata.title || template.metadata.name) != null ? _a : ""}`,
|
|
134
|
+
type: (_b = template.spec.type) != null ? _b : "",
|
|
135
|
+
description: (_c = template.metadata.description) != null ? _c : "-",
|
|
136
|
+
tags: (_e = (_d = template.metadata) == null ? void 0 : _d.tags) != null ? _e : []
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
const DeprecationWarning = () => {
|
|
140
|
+
const styles = useDeprecationStyles();
|
|
141
|
+
const Title = /* @__PURE__ */ React.createElement(Typography, {
|
|
142
|
+
style: { padding: 10, maxWidth: 300 }
|
|
143
|
+
}, "This template uses a syntax that has been deprecated, and should be migrated to a newer syntax. Click for more info.");
|
|
144
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
145
|
+
className: styles.deprecationIcon
|
|
146
|
+
}, /* @__PURE__ */ React.createElement(Tooltip, {
|
|
147
|
+
title: Title
|
|
148
|
+
}, /* @__PURE__ */ React.createElement(Link, {
|
|
149
|
+
href: "https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3",
|
|
150
|
+
className: styles.link
|
|
151
|
+
}, /* @__PURE__ */ React.createElement(WarningIcon, null))));
|
|
152
|
+
};
|
|
153
|
+
const TemplateCard = ({ template, deprecated }) => {
|
|
154
|
+
var _a;
|
|
155
|
+
const backstageTheme = useTheme();
|
|
156
|
+
const templateRoute = useRouteRef(selectedTemplateRouteRef);
|
|
157
|
+
const templateProps = getTemplateCardProps(template);
|
|
158
|
+
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
|
|
159
|
+
const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
|
|
160
|
+
const theme = backstageTheme.getPageTheme({ themeId });
|
|
161
|
+
const classes = useStyles$2({ backgroundImage: theme.backgroundImage });
|
|
162
|
+
const href = templateRoute({ templateName: templateProps.name });
|
|
163
|
+
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
|
|
164
|
+
const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
|
|
165
|
+
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardMedia, {
|
|
166
|
+
className: classes.cardHeader
|
|
167
|
+
}, /* @__PURE__ */ React.createElement(FavoriteEntity, {
|
|
168
|
+
className: classes.starButton,
|
|
169
|
+
entity: template
|
|
170
|
+
}), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(ItemCardHeader, {
|
|
171
|
+
title: templateProps.title,
|
|
172
|
+
subtitle: templateProps.type,
|
|
173
|
+
classes: { root: classes.title }
|
|
174
|
+
})), /* @__PURE__ */ React.createElement(CardContent, {
|
|
175
|
+
style: { display: "grid" }
|
|
176
|
+
}, /* @__PURE__ */ React.createElement(Box, {
|
|
177
|
+
className: classes.box
|
|
178
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
179
|
+
variant: "body2",
|
|
180
|
+
className: classes.label
|
|
181
|
+
}, "Description"), /* @__PURE__ */ React.createElement(MarkdownContent, {
|
|
182
|
+
content: templateProps.description
|
|
183
|
+
})), /* @__PURE__ */ React.createElement(Box, {
|
|
184
|
+
className: classes.box
|
|
185
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
186
|
+
variant: "body2",
|
|
187
|
+
className: classes.label
|
|
188
|
+
}, "Owner"), /* @__PURE__ */ React.createElement(EntityRefLinks, {
|
|
189
|
+
entityRefs: ownedByRelations,
|
|
190
|
+
defaultKind: "Group"
|
|
191
|
+
})), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, {
|
|
192
|
+
variant: "body2",
|
|
193
|
+
className: classes.label
|
|
194
|
+
}, "Tags"), (_a = templateProps.tags) == null ? void 0 : _a.map((tag) => /* @__PURE__ */ React.createElement(Chip, {
|
|
195
|
+
size: "small",
|
|
196
|
+
label: tag,
|
|
197
|
+
key: tag
|
|
198
|
+
})))), /* @__PURE__ */ React.createElement(CardActions, null, sourceLocation && /* @__PURE__ */ React.createElement(IconButton, {
|
|
199
|
+
className: classes.leftButton,
|
|
200
|
+
href: sourceLocation.locationTargetUrl
|
|
201
|
+
}, /* @__PURE__ */ React.createElement(ScmIntegrationIcon, {
|
|
202
|
+
type: sourceLocation.integrationType
|
|
203
|
+
})), /* @__PURE__ */ React.createElement(Button, {
|
|
204
|
+
color: "primary",
|
|
205
|
+
to: href,
|
|
206
|
+
"aria-label": `Choose ${templateProps.title}`
|
|
207
|
+
}, "Choose")));
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const TemplateList = ({
|
|
211
|
+
TemplateCardComponent,
|
|
212
|
+
group
|
|
213
|
+
}) => {
|
|
214
|
+
const { loading, error, entities } = useEntityList();
|
|
215
|
+
const Card = TemplateCardComponent || TemplateCard;
|
|
216
|
+
const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
|
|
217
|
+
const titleComponent = (() => {
|
|
218
|
+
if (group && group.title) {
|
|
219
|
+
if (typeof group.title === "string") {
|
|
220
|
+
return /* @__PURE__ */ React.createElement(ContentHeader, {
|
|
221
|
+
title: group.title
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return group.title;
|
|
225
|
+
}
|
|
226
|
+
return /* @__PURE__ */ React.createElement(ContentHeader, {
|
|
227
|
+
title: "Other Templates"
|
|
228
|
+
});
|
|
229
|
+
})();
|
|
230
|
+
if (group && maybeFilteredEntities.length === 0) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(WarningPanel, {
|
|
234
|
+
title: "Oops! Something went wrong loading the templates"
|
|
235
|
+
}, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, {
|
|
236
|
+
variant: "body2"
|
|
237
|
+
}, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link$1, {
|
|
238
|
+
to: "https://backstage.io/docs/features/software-templates/adding-templates"
|
|
239
|
+
}, "adding templates"), "."), /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(ItemCardGrid, null, maybeFilteredEntities && (maybeFilteredEntities == null ? void 0 : maybeFilteredEntities.length) > 0 && maybeFilteredEntities.map((template) => /* @__PURE__ */ React.createElement(Card, {
|
|
240
|
+
key: stringifyEntityRef(template),
|
|
241
|
+
template,
|
|
242
|
+
deprecated: template.apiVersion === "backstage.io/v1beta2"
|
|
243
|
+
})))));
|
|
244
|
+
};
|
|
245
|
+
|
|
85
246
|
const ScaffolderPageContents = ({
|
|
86
247
|
TemplateCardComponent,
|
|
87
248
|
groups
|
|
88
249
|
}) => {
|
|
89
|
-
const styles = useStyles$1();
|
|
90
250
|
const registerComponentLink = useRouteRef(registerComponentRouteRef);
|
|
91
251
|
const otherTemplatesGroup = {
|
|
92
252
|
title: groups ? "Other Templates" : "Templates",
|
|
@@ -100,24 +260,20 @@ const ScaffolderPageContents = ({
|
|
|
100
260
|
themeId: "home"
|
|
101
261
|
}, /* @__PURE__ */ React.createElement(Header, {
|
|
102
262
|
pageTitleOverride: "Create a New Component",
|
|
103
|
-
title:
|
|
104
|
-
shorthand: true
|
|
105
|
-
})),
|
|
263
|
+
title: "Create a New Component",
|
|
106
264
|
subtitle: "Create new software components using standard templates"
|
|
107
265
|
}), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, {
|
|
108
266
|
title: "Available Templates"
|
|
109
267
|
}, allowed && /* @__PURE__ */ React.createElement(CreateButton, {
|
|
110
268
|
title: "Register Existing Component",
|
|
111
269
|
to: registerComponentLink && registerComponentLink()
|
|
112
|
-
}), /* @__PURE__ */ React.createElement(SupportButton, null, "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).")), /* @__PURE__ */ React.createElement(
|
|
113
|
-
className: styles.contentWrapper
|
|
114
|
-
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(EntitySearchBar, null), /* @__PURE__ */ React.createElement(EntityKindPicker, {
|
|
270
|
+
}), /* @__PURE__ */ React.createElement(SupportButton, null, "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).")), /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(EntitySearchBar, null), /* @__PURE__ */ React.createElement(EntityKindPicker, {
|
|
115
271
|
initialFilter: "template",
|
|
116
272
|
hidden: true
|
|
117
273
|
}), /* @__PURE__ */ React.createElement(UserListPicker, {
|
|
118
274
|
initialFilter: "all",
|
|
119
275
|
availableFilters: ["all", "starred"]
|
|
120
|
-
}), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(
|
|
276
|
+
}), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(TemplateList, {
|
|
121
277
|
key: index,
|
|
122
278
|
TemplateCardComponent,
|
|
123
279
|
group
|
|
@@ -309,6 +465,9 @@ const MultistepJsonForm = (props) => {
|
|
|
309
465
|
};
|
|
310
466
|
const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
|
|
311
467
|
const handleCreate = async () => {
|
|
468
|
+
if (!onFinish) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
312
471
|
setDisableButtons(true);
|
|
313
472
|
try {
|
|
314
473
|
await onFinish();
|
|
@@ -345,10 +504,10 @@ const MultistepJsonForm = (props) => {
|
|
|
345
504
|
},
|
|
346
505
|
...formProps,
|
|
347
506
|
...transformSchemaToProps(schema)
|
|
348
|
-
}, /* @__PURE__ */ React.createElement(Button, {
|
|
507
|
+
}, /* @__PURE__ */ React.createElement(Button$1, {
|
|
349
508
|
disabled: activeStep === 0,
|
|
350
509
|
onClick: handleBack
|
|
351
|
-
}, "Back"), /* @__PURE__ */ React.createElement(Button, {
|
|
510
|
+
}, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
|
|
352
511
|
variant: "contained",
|
|
353
512
|
color: "primary",
|
|
354
513
|
type: "submit"
|
|
@@ -363,17 +522,17 @@ const MultistepJsonForm = (props) => {
|
|
|
363
522
|
metadata: getReviewData(formData, steps)
|
|
364
523
|
}), /* @__PURE__ */ React.createElement(Box, {
|
|
365
524
|
mb: 4
|
|
366
|
-
}), /* @__PURE__ */ React.createElement(Button, {
|
|
525
|
+
}), /* @__PURE__ */ React.createElement(Button$1, {
|
|
367
526
|
onClick: handleBack,
|
|
368
527
|
disabled: disableButtons
|
|
369
|
-
}, "Back"), /* @__PURE__ */ React.createElement(Button, {
|
|
528
|
+
}, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
|
|
370
529
|
onClick: handleReset,
|
|
371
530
|
disabled: disableButtons
|
|
372
|
-
}, "Reset"), /* @__PURE__ */ React.createElement(Button, {
|
|
531
|
+
}, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
|
|
373
532
|
variant: "contained",
|
|
374
533
|
color: "primary",
|
|
375
534
|
onClick: handleCreate,
|
|
376
|
-
disabled: disableButtons
|
|
535
|
+
disabled: !onFinish || disableButtons
|
|
377
536
|
}, "Create"))));
|
|
378
537
|
};
|
|
379
538
|
|
|
@@ -471,9 +630,7 @@ const TemplatePage = ({
|
|
|
471
630
|
themeId: "home"
|
|
472
631
|
}, /* @__PURE__ */ React.createElement(Header, {
|
|
473
632
|
pageTitleOverride: "Create a New Component",
|
|
474
|
-
title:
|
|
475
|
-
shorthand: true
|
|
476
|
-
})),
|
|
633
|
+
title: "Create a New Component",
|
|
477
634
|
subtitle: "Create new software components using standard templates"
|
|
478
635
|
}), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, {
|
|
479
636
|
"data-testid": "loading-progress"
|
|
@@ -496,7 +653,7 @@ const TemplatePage = ({
|
|
|
496
653
|
}))));
|
|
497
654
|
};
|
|
498
655
|
|
|
499
|
-
const useStyles = makeStyles((theme) => ({
|
|
656
|
+
const useStyles$1 = makeStyles((theme) => ({
|
|
500
657
|
code: {
|
|
501
658
|
fontFamily: "Menlo, monospace",
|
|
502
659
|
padding: theme.spacing(1),
|
|
@@ -519,7 +676,7 @@ const useStyles = makeStyles((theme) => ({
|
|
|
519
676
|
}));
|
|
520
677
|
const ActionsPage = () => {
|
|
521
678
|
const api = useApi(scaffolderApiRef);
|
|
522
|
-
const classes = useStyles();
|
|
679
|
+
const classes = useStyles$1();
|
|
523
680
|
const { loading, value, error } = useAsync(async () => {
|
|
524
681
|
return api.listActions();
|
|
525
682
|
});
|
|
@@ -604,20 +761,169 @@ const ActionsPage = () => {
|
|
|
604
761
|
}), /* @__PURE__ */ React.createElement(Content, null, items));
|
|
605
762
|
};
|
|
606
763
|
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
764
|
+
const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
|
|
765
|
+
parameters:
|
|
766
|
+
- title: Fill in some steps
|
|
767
|
+
required:
|
|
768
|
+
- name
|
|
769
|
+
properties:
|
|
770
|
+
name:
|
|
771
|
+
title: Name
|
|
772
|
+
type: string
|
|
773
|
+
description: Unique name of the component
|
|
774
|
+
owner:
|
|
775
|
+
title: Owner
|
|
776
|
+
type: string
|
|
777
|
+
description: Owner of the component
|
|
778
|
+
ui:field: OwnerPicker
|
|
779
|
+
ui:options:
|
|
780
|
+
allowedKinds:
|
|
781
|
+
- Group
|
|
782
|
+
- title: Choose a location
|
|
783
|
+
required:
|
|
784
|
+
- repoUrl
|
|
785
|
+
properties:
|
|
786
|
+
repoUrl:
|
|
787
|
+
title: Repository Location
|
|
788
|
+
type: string
|
|
789
|
+
ui:field: RepoUrlPicker
|
|
790
|
+
ui:options:
|
|
791
|
+
allowedHosts:
|
|
792
|
+
- github.com
|
|
793
|
+
`;
|
|
794
|
+
const useStyles = makeStyles({
|
|
795
|
+
templateSelect: {
|
|
796
|
+
marginBottom: "10px"
|
|
797
|
+
},
|
|
798
|
+
grid: {
|
|
799
|
+
height: "100%"
|
|
800
|
+
},
|
|
801
|
+
codeMirror: {
|
|
802
|
+
height: "95%"
|
|
617
803
|
}
|
|
804
|
+
});
|
|
805
|
+
const TemplatePreviewPage = ({
|
|
806
|
+
defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
|
|
807
|
+
customFieldExtensions = []
|
|
808
|
+
}) => {
|
|
809
|
+
const classes = useStyles();
|
|
810
|
+
const alertApi = useApi(alertApiRef);
|
|
811
|
+
const catalogApi = useApi(catalogApiRef);
|
|
812
|
+
const apiHolder = useApiHolder();
|
|
813
|
+
const [selectedTemplate, setSelectedTemplate] = useState("");
|
|
814
|
+
const [schema, setSchema] = useState({
|
|
815
|
+
title: "",
|
|
816
|
+
steps: []
|
|
817
|
+
});
|
|
818
|
+
const [templateOptions, setTemplateOptions] = useState([]);
|
|
819
|
+
const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
|
|
820
|
+
const [formState, setFormState] = useState({});
|
|
821
|
+
const { loading } = useAsync(() => catalogApi.getEntities({
|
|
822
|
+
filter: { kind: "template" },
|
|
823
|
+
fields: [
|
|
824
|
+
"kind",
|
|
825
|
+
"metadata.namespace",
|
|
826
|
+
"metadata.name",
|
|
827
|
+
"metadata.title",
|
|
828
|
+
"spec.parameters"
|
|
829
|
+
]
|
|
830
|
+
}).then(({ items }) => setTemplateOptions(items.map((template) => {
|
|
831
|
+
var _a;
|
|
832
|
+
return {
|
|
833
|
+
label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
|
|
834
|
+
value: template
|
|
835
|
+
};
|
|
836
|
+
}))).catch((e) => alertApi.post({
|
|
837
|
+
message: `Error loading exisiting templates: ${e.message}`,
|
|
838
|
+
severity: "error"
|
|
839
|
+
})), [catalogApi]);
|
|
840
|
+
const errorPanel = document.createElement("div");
|
|
841
|
+
errorPanel.style.color = "red";
|
|
842
|
+
useDebounce(() => {
|
|
843
|
+
try {
|
|
844
|
+
const parsedTemplate = yaml.parse(templateYaml);
|
|
845
|
+
setSchema({
|
|
846
|
+
title: "Preview",
|
|
847
|
+
steps: parsedTemplate.parameters.map((param) => ({
|
|
848
|
+
title: param.title,
|
|
849
|
+
schema: param
|
|
850
|
+
}))
|
|
851
|
+
});
|
|
852
|
+
setFormState({});
|
|
853
|
+
} catch (e) {
|
|
854
|
+
errorPanel.textContent = e.message;
|
|
855
|
+
}
|
|
856
|
+
}, 250, [setFormState, setSchema, templateYaml]);
|
|
857
|
+
const handleSelectChange = useCallback((selected) => {
|
|
858
|
+
setSelectedTemplate(selected);
|
|
859
|
+
setTemplateYaml(yaml.stringify(selected.spec));
|
|
860
|
+
}, [setTemplateYaml]);
|
|
861
|
+
const handleFormReset = () => setFormState({});
|
|
862
|
+
const handleFormChange = useCallback((e) => setFormState(e.formData), [setFormState]);
|
|
863
|
+
const handleCodeChange = useCallback((code) => {
|
|
864
|
+
setTemplateYaml(code);
|
|
865
|
+
}, [setTemplateYaml]);
|
|
866
|
+
const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
|
|
867
|
+
const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
|
|
868
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
869
|
+
themeId: "home"
|
|
870
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
871
|
+
title: "Template Preview",
|
|
872
|
+
subtitle: "Preview your template parameter UI"
|
|
873
|
+
}), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement(Grid, {
|
|
874
|
+
container: true,
|
|
875
|
+
className: classes.grid
|
|
876
|
+
}, /* @__PURE__ */ React.createElement(Grid, {
|
|
877
|
+
item: true,
|
|
878
|
+
xs: 6
|
|
879
|
+
}, /* @__PURE__ */ React.createElement(FormControl, {
|
|
880
|
+
className: classes.templateSelect,
|
|
881
|
+
variant: "outlined",
|
|
882
|
+
fullWidth: true
|
|
883
|
+
}, /* @__PURE__ */ React.createElement(InputLabel, {
|
|
884
|
+
id: "select-template-label"
|
|
885
|
+
}, "Load Existing Template"), /* @__PURE__ */ React.createElement(Select, {
|
|
886
|
+
value: selectedTemplate,
|
|
887
|
+
label: "Load Existing Template",
|
|
888
|
+
labelId: "select-template-label",
|
|
889
|
+
onChange: (e) => handleSelectChange(e.target.value)
|
|
890
|
+
}, templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, {
|
|
891
|
+
key: idx,
|
|
892
|
+
value: option.value
|
|
893
|
+
}, option.label)))), /* @__PURE__ */ React.createElement(CodeMirror, {
|
|
894
|
+
className: classes.codeMirror,
|
|
895
|
+
value: templateYaml,
|
|
896
|
+
theme: "dark",
|
|
897
|
+
height: "100%",
|
|
898
|
+
extensions: [
|
|
899
|
+
StreamLanguage.define(yaml$1),
|
|
900
|
+
showPanel.of(() => ({ dom: errorPanel, top: true }))
|
|
901
|
+
],
|
|
902
|
+
onChange: handleCodeChange
|
|
903
|
+
})), /* @__PURE__ */ React.createElement(Grid, {
|
|
904
|
+
item: true,
|
|
905
|
+
xs: 6
|
|
906
|
+
}, schema && /* @__PURE__ */ React.createElement(InfoCard, {
|
|
907
|
+
key: JSON.stringify(schema)
|
|
908
|
+
}, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
|
|
909
|
+
formData: formState,
|
|
910
|
+
fields: customFieldComponents,
|
|
911
|
+
onChange: handleFormChange,
|
|
912
|
+
onReset: handleFormReset,
|
|
913
|
+
steps: schema.steps.map((step) => {
|
|
914
|
+
return {
|
|
915
|
+
...step,
|
|
916
|
+
validate: createValidator(step.schema, customFieldValidators, { apiHolder })
|
|
917
|
+
};
|
|
918
|
+
})
|
|
919
|
+
}))))));
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const Router = (props) => {
|
|
923
|
+
const { groups, components = {}, defaultPreviewTemplate } = props;
|
|
618
924
|
const { TemplateCardComponent, TaskPageComponent } = components;
|
|
619
925
|
const outlet = useOutlet();
|
|
620
|
-
const TaskPageElement =
|
|
926
|
+
const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
|
|
621
927
|
const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
|
|
622
928
|
key: FIELD_EXTENSION_WRAPPER_KEY
|
|
623
929
|
}).findComponentData({
|
|
@@ -631,7 +937,7 @@ const Router = (props) => {
|
|
|
631
937
|
path: "/",
|
|
632
938
|
element: /* @__PURE__ */ React.createElement(ScaffolderPage, {
|
|
633
939
|
groups,
|
|
634
|
-
TemplateCardComponent
|
|
940
|
+
TemplateCardComponent
|
|
635
941
|
})
|
|
636
942
|
}), /* @__PURE__ */ React.createElement(Route, {
|
|
637
943
|
path: "/templates/:templateName",
|
|
@@ -644,8 +950,14 @@ const Router = (props) => {
|
|
|
644
950
|
}), /* @__PURE__ */ React.createElement(Route, {
|
|
645
951
|
path: "/actions",
|
|
646
952
|
element: /* @__PURE__ */ React.createElement(ActionsPage, null)
|
|
953
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
954
|
+
path: "/preview",
|
|
955
|
+
element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePreviewPage, {
|
|
956
|
+
defaultPreviewTemplate,
|
|
957
|
+
customFieldExtensions: fieldExtensions
|
|
958
|
+
}))
|
|
647
959
|
}));
|
|
648
960
|
};
|
|
649
961
|
|
|
650
962
|
export { Router };
|
|
651
|
-
//# sourceMappingURL=Router-
|
|
963
|
+
//# sourceMappingURL=Router-773d053b.esm.js.map
|