@backstage/plugin-scaffolder 1.2.0 → 1.3.0-next.2
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 +66 -0
- package/alpha/package.json +1 -1
- package/dist/esm/Router-09b6a7c3.esm.js +2380 -0
- package/dist/esm/Router-09b6a7c3.esm.js.map +1 -0
- package/dist/esm/{default-67a72a06.esm.js → default-554cb9ad.esm.js} +2 -2
- package/dist/esm/{default-67a72a06.esm.js.map → default-554cb9ad.esm.js.map} +1 -1
- package/dist/esm/{index-6a9fc0c0.esm.js → index-b64713a1.esm.js} +95 -14
- package/dist/esm/index-b64713a1.esm.js.map +1 -0
- package/dist/esm/{index-d5c1fdb0.esm.js → index-f46ffb89.esm.js} +3 -3
- package/dist/esm/{index-d5c1fdb0.esm.js.map → index-f46ffb89.esm.js.map} +1 -1
- package/dist/index.alpha.d.ts +40 -0
- package/dist/index.beta.d.ts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.esm.js +1 -1
- package/package.json +20 -18
- package/dist/esm/Router-b7921312.esm.js +0 -1013
- package/dist/esm/Router-b7921312.esm.js.map +0 -1
- package/dist/esm/index-6a9fc0c0.esm.js.map +0 -1
|
@@ -0,0 +1,2380 @@
|
|
|
1
|
+
import React, { useState, useContext, useCallback, createContext, useEffect, useRef, useMemo, Children, Component, Fragment } from 'react';
|
|
2
|
+
import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router';
|
|
3
|
+
import { ItemCardHeader, MarkdownContent, Button, ContentHeader, Progress, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage, ErrorPanel, LogViewer, StatusError, StatusOK, StatusPending, Lifecycle, EmptyState } 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, EntityRefLink } from '@backstage/plugin-catalog-react';
|
|
6
|
+
import { s as selectedTemplateRouteRef, e as editRouteRef, a as actionsRouteRef, b as scaffolderListTaskRouteRef, r as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, c as scaffolderApiRef, d as scaffolderTaskRouteRef, f as rootRouteRef, g as TaskStatusStepper, h as TaskPageLinks, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, j as SecretsContextProvider, k as TaskPage } from './index-b64713a1.esm.js';
|
|
7
|
+
import { RELATION_OWNED_BY, stringifyEntityRef, parseEntityRef } 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, Divider as Divider$1, FormControl, InputLabel, Select, MenuItem as MenuItem$1, List as List$2, ListItemIcon as ListItemIcon$1, ListItemText as ListItemText$1 } from '@material-ui/core';
|
|
9
|
+
import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
|
|
10
|
+
import WarningIcon from '@material-ui/icons/Warning';
|
|
11
|
+
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
|
|
12
|
+
import { usePermission } from '@backstage/plugin-permission-react';
|
|
13
|
+
import IconButton$1 from '@material-ui/core/IconButton';
|
|
14
|
+
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
|
15
|
+
import ListItemText from '@material-ui/core/ListItemText';
|
|
16
|
+
import MenuItem from '@material-ui/core/MenuItem';
|
|
17
|
+
import MenuList from '@material-ui/core/MenuList';
|
|
18
|
+
import Popover from '@material-ui/core/Popover';
|
|
19
|
+
import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
|
|
20
|
+
import Description from '@material-ui/icons/Description';
|
|
21
|
+
import Edit from '@material-ui/icons/Edit';
|
|
22
|
+
import List from '@material-ui/icons/List';
|
|
23
|
+
import MoreVert from '@material-ui/icons/MoreVert';
|
|
24
|
+
import qs from 'qs';
|
|
25
|
+
import { useParams } from 'react-router-dom';
|
|
26
|
+
import useAsync from 'react-use/lib/useAsync';
|
|
27
|
+
import { withTheme } from '@rjsf/core';
|
|
28
|
+
import { Theme } from '@rjsf/material-ui';
|
|
29
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
30
|
+
import classNames from 'classnames';
|
|
31
|
+
import Card$1 from '@material-ui/core/Card';
|
|
32
|
+
import CardActionArea from '@material-ui/core/CardActionArea';
|
|
33
|
+
import CardContent$1 from '@material-ui/core/CardContent';
|
|
34
|
+
import Tooltip$1 from '@material-ui/core/Tooltip';
|
|
35
|
+
import Typography$1 from '@material-ui/core/Typography';
|
|
36
|
+
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
|
|
37
|
+
import { useAsync as useAsync$1, useRerender, usePrevious, useKeyboardEvent } from '@react-hookz/web';
|
|
38
|
+
import yaml from 'yaml';
|
|
39
|
+
import Accordion from '@material-ui/core/Accordion';
|
|
40
|
+
import AccordionDetails from '@material-ui/core/AccordionDetails';
|
|
41
|
+
import AccordionSummary from '@material-ui/core/AccordionSummary';
|
|
42
|
+
import Divider from '@material-ui/core/Divider';
|
|
43
|
+
import ExpandMoreIcon$1 from '@material-ui/icons/ExpandLess';
|
|
44
|
+
import List$1 from '@material-ui/core/List';
|
|
45
|
+
import ListItem from '@material-ui/core/ListItem';
|
|
46
|
+
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
|
47
|
+
import Cancel from '@material-ui/icons/Cancel';
|
|
48
|
+
import Check from '@material-ui/icons/Check';
|
|
49
|
+
import DeleteIcon from '@material-ui/icons/Delete';
|
|
50
|
+
import { StreamLanguage } from '@codemirror/language';
|
|
51
|
+
import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
|
|
52
|
+
import Box$1 from '@material-ui/core/Box';
|
|
53
|
+
import Tab from '@material-ui/core/Tab';
|
|
54
|
+
import Tabs from '@material-ui/core/Tabs';
|
|
55
|
+
import CodeMirror from '@uiw/react-codemirror';
|
|
56
|
+
import TreeView from '@material-ui/lab/TreeView';
|
|
57
|
+
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
|
58
|
+
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
|
59
|
+
import TreeItem from '@material-ui/lab/TreeItem';
|
|
60
|
+
import CloseIcon from '@material-ui/icons/Close';
|
|
61
|
+
import RefreshIcon from '@material-ui/icons/Refresh';
|
|
62
|
+
import SaveIcon from '@material-ui/icons/Save';
|
|
63
|
+
import useDebounce from 'react-use/lib/useDebounce';
|
|
64
|
+
import { showPanel } from '@codemirror/view';
|
|
65
|
+
import MaterialTable from '@material-table/core';
|
|
66
|
+
import SettingsIcon from '@material-ui/icons/Settings';
|
|
67
|
+
import AllIcon from '@material-ui/icons/FontDownload';
|
|
68
|
+
import { DateTime, Interval } from 'luxon';
|
|
69
|
+
import humanizeDuration from 'humanize-duration';
|
|
70
|
+
import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default-554cb9ad.esm.js';
|
|
71
|
+
import '@backstage/errors';
|
|
72
|
+
import 'zen-observable';
|
|
73
|
+
import '@material-ui/core/FormControl';
|
|
74
|
+
import '@material-ui/lab/Autocomplete';
|
|
75
|
+
import 'react-use/lib/useEffectOnce';
|
|
76
|
+
import '@material-ui/lab';
|
|
77
|
+
import '@material-ui/core/FormHelperText';
|
|
78
|
+
import '@material-ui/core/Input';
|
|
79
|
+
import '@material-ui/core/InputLabel';
|
|
80
|
+
import 'lodash/capitalize';
|
|
81
|
+
import '@material-ui/icons/CheckBox';
|
|
82
|
+
import '@material-ui/icons/CheckBoxOutlineBlank';
|
|
83
|
+
import '@material-ui/core/Grid';
|
|
84
|
+
import '@material-ui/core/Step';
|
|
85
|
+
import '@material-ui/core/StepLabel';
|
|
86
|
+
import '@material-ui/core/Stepper';
|
|
87
|
+
import '@material-ui/icons/FiberManualRecord';
|
|
88
|
+
import 'react-use/lib/useInterval';
|
|
89
|
+
import 'use-immer';
|
|
90
|
+
import '@material-ui/icons/Language';
|
|
91
|
+
|
|
92
|
+
const useStyles$e = makeStyles((theme) => ({
|
|
93
|
+
cardHeader: {
|
|
94
|
+
position: "relative"
|
|
95
|
+
},
|
|
96
|
+
title: {
|
|
97
|
+
backgroundImage: ({ backgroundImage }) => backgroundImage
|
|
98
|
+
},
|
|
99
|
+
box: {
|
|
100
|
+
overflow: "hidden",
|
|
101
|
+
textOverflow: "ellipsis",
|
|
102
|
+
display: "-webkit-box",
|
|
103
|
+
"-webkit-line-clamp": 10,
|
|
104
|
+
"-webkit-box-orient": "vertical",
|
|
105
|
+
paddingBottom: "0.8em"
|
|
106
|
+
},
|
|
107
|
+
label: {
|
|
108
|
+
color: theme.palette.text.secondary,
|
|
109
|
+
textTransform: "uppercase",
|
|
110
|
+
fontSize: "0.65rem",
|
|
111
|
+
fontWeight: "bold",
|
|
112
|
+
letterSpacing: 0.5,
|
|
113
|
+
lineHeight: 1,
|
|
114
|
+
paddingBottom: "0.2rem"
|
|
115
|
+
},
|
|
116
|
+
leftButton: {
|
|
117
|
+
marginRight: "auto"
|
|
118
|
+
},
|
|
119
|
+
starButton: {
|
|
120
|
+
position: "absolute",
|
|
121
|
+
top: theme.spacing(0.5),
|
|
122
|
+
right: theme.spacing(0.5),
|
|
123
|
+
padding: "0.25rem",
|
|
124
|
+
color: "#fff"
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
const useDeprecationStyles = makeStyles((theme) => ({
|
|
128
|
+
deprecationIcon: {
|
|
129
|
+
position: "absolute",
|
|
130
|
+
top: theme.spacing(0.5),
|
|
131
|
+
right: theme.spacing(3.5),
|
|
132
|
+
padding: "0.25rem"
|
|
133
|
+
},
|
|
134
|
+
link: {
|
|
135
|
+
color: theme.palette.warning.light
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
const getTemplateCardProps = (template) => {
|
|
139
|
+
var _a, _b, _c, _d, _e;
|
|
140
|
+
return {
|
|
141
|
+
key: template.metadata.uid,
|
|
142
|
+
name: template.metadata.name,
|
|
143
|
+
title: `${(_a = template.metadata.title || template.metadata.name) != null ? _a : ""}`,
|
|
144
|
+
type: (_b = template.spec.type) != null ? _b : "",
|
|
145
|
+
description: (_c = template.metadata.description) != null ? _c : "-",
|
|
146
|
+
tags: (_e = (_d = template.metadata) == null ? void 0 : _d.tags) != null ? _e : []
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
const DeprecationWarning = () => {
|
|
150
|
+
const styles = useDeprecationStyles();
|
|
151
|
+
const Title = /* @__PURE__ */ React.createElement(Typography, {
|
|
152
|
+
style: { padding: 10, maxWidth: 300 }
|
|
153
|
+
}, "This template uses a syntax that has been deprecated, and should be migrated to a newer syntax. Click for more info.");
|
|
154
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
155
|
+
className: styles.deprecationIcon
|
|
156
|
+
}, /* @__PURE__ */ React.createElement(Tooltip, {
|
|
157
|
+
title: Title
|
|
158
|
+
}, /* @__PURE__ */ React.createElement(Link, {
|
|
159
|
+
href: "https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3",
|
|
160
|
+
className: styles.link
|
|
161
|
+
}, /* @__PURE__ */ React.createElement(WarningIcon, null))));
|
|
162
|
+
};
|
|
163
|
+
const TemplateCard = ({ template, deprecated }) => {
|
|
164
|
+
var _a;
|
|
165
|
+
const backstageTheme = useTheme();
|
|
166
|
+
const templateRoute = useRouteRef(selectedTemplateRouteRef);
|
|
167
|
+
const templateProps = getTemplateCardProps(template);
|
|
168
|
+
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
|
|
169
|
+
const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
|
|
170
|
+
const theme = backstageTheme.getPageTheme({ themeId });
|
|
171
|
+
const classes = useStyles$e({ backgroundImage: theme.backgroundImage });
|
|
172
|
+
const href = templateRoute({ templateName: templateProps.name });
|
|
173
|
+
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
|
|
174
|
+
const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
|
|
175
|
+
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardMedia, {
|
|
176
|
+
className: classes.cardHeader
|
|
177
|
+
}, /* @__PURE__ */ React.createElement(FavoriteEntity, {
|
|
178
|
+
className: classes.starButton,
|
|
179
|
+
entity: template
|
|
180
|
+
}), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(ItemCardHeader, {
|
|
181
|
+
title: templateProps.title,
|
|
182
|
+
subtitle: templateProps.type,
|
|
183
|
+
classes: { root: classes.title }
|
|
184
|
+
})), /* @__PURE__ */ React.createElement(CardContent, {
|
|
185
|
+
style: { display: "grid" }
|
|
186
|
+
}, /* @__PURE__ */ React.createElement(Box, {
|
|
187
|
+
className: classes.box
|
|
188
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
189
|
+
variant: "body2",
|
|
190
|
+
className: classes.label
|
|
191
|
+
}, "Description"), /* @__PURE__ */ React.createElement(MarkdownContent, {
|
|
192
|
+
content: templateProps.description
|
|
193
|
+
})), /* @__PURE__ */ React.createElement(Box, {
|
|
194
|
+
className: classes.box
|
|
195
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
196
|
+
variant: "body2",
|
|
197
|
+
className: classes.label
|
|
198
|
+
}, "Owner"), /* @__PURE__ */ React.createElement(EntityRefLinks, {
|
|
199
|
+
entityRefs: ownedByRelations,
|
|
200
|
+
defaultKind: "Group"
|
|
201
|
+
})), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, {
|
|
202
|
+
variant: "body2",
|
|
203
|
+
className: classes.label
|
|
204
|
+
}, "Tags"), (_a = templateProps.tags) == null ? void 0 : _a.map((tag) => /* @__PURE__ */ React.createElement(Chip, {
|
|
205
|
+
size: "small",
|
|
206
|
+
label: tag,
|
|
207
|
+
key: tag
|
|
208
|
+
})))), /* @__PURE__ */ React.createElement(CardActions, null, sourceLocation && /* @__PURE__ */ React.createElement(IconButton, {
|
|
209
|
+
className: classes.leftButton,
|
|
210
|
+
href: sourceLocation.locationTargetUrl
|
|
211
|
+
}, /* @__PURE__ */ React.createElement(ScmIntegrationIcon, {
|
|
212
|
+
type: sourceLocation.integrationType
|
|
213
|
+
})), /* @__PURE__ */ React.createElement(Button, {
|
|
214
|
+
color: "primary",
|
|
215
|
+
to: href,
|
|
216
|
+
"aria-label": `Choose ${templateProps.title}`
|
|
217
|
+
}, "Choose")));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const TemplateList = ({
|
|
221
|
+
TemplateCardComponent,
|
|
222
|
+
group
|
|
223
|
+
}) => {
|
|
224
|
+
const { loading, error, entities } = useEntityList();
|
|
225
|
+
const Card = TemplateCardComponent || TemplateCard;
|
|
226
|
+
const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
|
|
227
|
+
const titleComponent = (() => {
|
|
228
|
+
if (group && group.title) {
|
|
229
|
+
if (typeof group.title === "string") {
|
|
230
|
+
return /* @__PURE__ */ React.createElement(ContentHeader, {
|
|
231
|
+
title: group.title
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return group.title;
|
|
235
|
+
}
|
|
236
|
+
return /* @__PURE__ */ React.createElement(ContentHeader, {
|
|
237
|
+
title: "Other Templates"
|
|
238
|
+
});
|
|
239
|
+
})();
|
|
240
|
+
if (group && maybeFilteredEntities.length === 0) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(WarningPanel, {
|
|
244
|
+
title: "Oops! Something went wrong loading the templates"
|
|
245
|
+
}, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, {
|
|
246
|
+
variant: "body2"
|
|
247
|
+
}, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link$1, {
|
|
248
|
+
to: "https://backstage.io/docs/features/software-templates/adding-templates"
|
|
249
|
+
}, "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, {
|
|
250
|
+
key: stringifyEntityRef(template),
|
|
251
|
+
template,
|
|
252
|
+
deprecated: template.apiVersion === "backstage.io/v1beta2"
|
|
253
|
+
})))));
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const useStyles$d = makeStyles$1({
|
|
257
|
+
button: {
|
|
258
|
+
color: "white"
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
function ScaffolderPageContextMenu(props) {
|
|
262
|
+
const classes = useStyles$d();
|
|
263
|
+
const [anchorEl, setAnchorEl] = useState();
|
|
264
|
+
const editLink = useRouteRef(editRouteRef);
|
|
265
|
+
const actionsLink = useRouteRef(actionsRouteRef);
|
|
266
|
+
const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
|
|
267
|
+
const navigate = useNavigate();
|
|
268
|
+
const showEditor = props.editor !== false;
|
|
269
|
+
const showActions = props.actions !== false;
|
|
270
|
+
const showTasks = props.tasks !== false;
|
|
271
|
+
if (!showEditor && !showActions) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const onOpen = (event) => {
|
|
275
|
+
setAnchorEl(event.currentTarget);
|
|
276
|
+
};
|
|
277
|
+
const onClose = () => {
|
|
278
|
+
setAnchorEl(void 0);
|
|
279
|
+
};
|
|
280
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(IconButton$1, {
|
|
281
|
+
"aria-label": "more",
|
|
282
|
+
"aria-controls": "long-menu",
|
|
283
|
+
"aria-haspopup": "true",
|
|
284
|
+
onClick: onOpen,
|
|
285
|
+
"data-testid": "menu-button",
|
|
286
|
+
color: "inherit",
|
|
287
|
+
className: classes.button
|
|
288
|
+
}, /* @__PURE__ */ React.createElement(MoreVert, null)), /* @__PURE__ */ React.createElement(Popover, {
|
|
289
|
+
open: Boolean(anchorEl),
|
|
290
|
+
onClose,
|
|
291
|
+
anchorEl,
|
|
292
|
+
anchorOrigin: { vertical: "bottom", horizontal: "right" },
|
|
293
|
+
transformOrigin: { vertical: "top", horizontal: "right" }
|
|
294
|
+
}, /* @__PURE__ */ React.createElement(MenuList, null, showEditor && /* @__PURE__ */ React.createElement(MenuItem, {
|
|
295
|
+
onClick: () => navigate(editLink())
|
|
296
|
+
}, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, {
|
|
297
|
+
fontSize: "small"
|
|
298
|
+
})), /* @__PURE__ */ React.createElement(ListItemText, {
|
|
299
|
+
primary: "Template Editor"
|
|
300
|
+
})), showActions && /* @__PURE__ */ React.createElement(MenuItem, {
|
|
301
|
+
onClick: () => navigate(actionsLink())
|
|
302
|
+
}, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Description, {
|
|
303
|
+
fontSize: "small"
|
|
304
|
+
})), /* @__PURE__ */ React.createElement(ListItemText, {
|
|
305
|
+
primary: "Installed Actions"
|
|
306
|
+
})), showTasks && /* @__PURE__ */ React.createElement(MenuItem, {
|
|
307
|
+
onClick: () => navigate(tasksLink())
|
|
308
|
+
}, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(List, {
|
|
309
|
+
fontSize: "small"
|
|
310
|
+
})), /* @__PURE__ */ React.createElement(ListItemText, {
|
|
311
|
+
primary: "Task List"
|
|
312
|
+
})))));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const ScaffolderPageContents = ({
|
|
316
|
+
TemplateCardComponent,
|
|
317
|
+
groups,
|
|
318
|
+
contextMenu
|
|
319
|
+
}) => {
|
|
320
|
+
const registerComponentLink = useRouteRef(registerComponentRouteRef);
|
|
321
|
+
const otherTemplatesGroup = {
|
|
322
|
+
title: groups ? "Other Templates" : "Templates",
|
|
323
|
+
filter: (entity) => {
|
|
324
|
+
const filtered = (groups != null ? groups : []).map((group) => group.filter(entity));
|
|
325
|
+
return !filtered.some((result) => result === true);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const { allowed } = usePermission({
|
|
329
|
+
permission: catalogEntityCreatePermission
|
|
330
|
+
});
|
|
331
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
332
|
+
themeId: "home"
|
|
333
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
334
|
+
pageTitleOverride: "Create a New Component",
|
|
335
|
+
title: "Create a New Component",
|
|
336
|
+
subtitle: "Create new software components using standard templates"
|
|
337
|
+
}, /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, {
|
|
338
|
+
...contextMenu
|
|
339
|
+
})), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, {
|
|
340
|
+
title: "Available Templates"
|
|
341
|
+
}, allowed && /* @__PURE__ */ React.createElement(CreateButton, {
|
|
342
|
+
title: "Register Existing Component",
|
|
343
|
+
to: registerComponentLink && registerComponentLink()
|
|
344
|
+
}), /* @__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, {
|
|
345
|
+
initialFilter: "template",
|
|
346
|
+
hidden: true
|
|
347
|
+
}), /* @__PURE__ */ React.createElement(UserListPicker, {
|
|
348
|
+
initialFilter: "all",
|
|
349
|
+
availableFilters: ["all", "starred"]
|
|
350
|
+
}), /* @__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, {
|
|
351
|
+
key: index,
|
|
352
|
+
TemplateCardComponent,
|
|
353
|
+
group
|
|
354
|
+
})), /* @__PURE__ */ React.createElement(TemplateList, {
|
|
355
|
+
key: "other",
|
|
356
|
+
TemplateCardComponent,
|
|
357
|
+
group: otherTemplatesGroup
|
|
358
|
+
})))));
|
|
359
|
+
};
|
|
360
|
+
const ScaffolderPage = ({
|
|
361
|
+
TemplateCardComponent,
|
|
362
|
+
groups
|
|
363
|
+
}) => /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(ScaffolderPageContents, {
|
|
364
|
+
TemplateCardComponent,
|
|
365
|
+
groups
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
function isObject$1(value) {
|
|
369
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
370
|
+
}
|
|
371
|
+
function extractUiSchema(schema, uiSchema) {
|
|
372
|
+
if (!isObject$1(schema)) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
|
|
376
|
+
for (const propName in schema) {
|
|
377
|
+
if (!schema.hasOwnProperty(propName)) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (propName.startsWith("ui:")) {
|
|
381
|
+
uiSchema[propName] = schema[propName];
|
|
382
|
+
delete schema[propName];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (isObject$1(properties)) {
|
|
386
|
+
for (const propName in properties) {
|
|
387
|
+
if (!properties.hasOwnProperty(propName)) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const schemaNode = properties[propName];
|
|
391
|
+
if (!isObject$1(schemaNode)) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const innerUiSchema = {};
|
|
395
|
+
uiSchema[propName] = innerUiSchema;
|
|
396
|
+
extractUiSchema(schemaNode, innerUiSchema);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (isObject$1(items)) {
|
|
400
|
+
const innerUiSchema = {};
|
|
401
|
+
uiSchema.items = innerUiSchema;
|
|
402
|
+
extractUiSchema(items, innerUiSchema);
|
|
403
|
+
}
|
|
404
|
+
if (Array.isArray(anyOf)) {
|
|
405
|
+
for (const schemaNode of anyOf) {
|
|
406
|
+
if (!isObject$1(schemaNode)) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (Array.isArray(oneOf)) {
|
|
413
|
+
for (const schemaNode of oneOf) {
|
|
414
|
+
if (!isObject$1(schemaNode)) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(allOf)) {
|
|
421
|
+
for (const schemaNode of allOf) {
|
|
422
|
+
if (!isObject$1(schemaNode)) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (isObject$1(dependencies)) {
|
|
429
|
+
for (const depName of Object.keys(dependencies)) {
|
|
430
|
+
const schemaNode = dependencies[depName];
|
|
431
|
+
if (!isObject$1(schemaNode)) {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function transformSchemaToProps(inputSchema) {
|
|
439
|
+
inputSchema.type = inputSchema.type || "object";
|
|
440
|
+
const schema = JSON.parse(JSON.stringify(inputSchema));
|
|
441
|
+
delete schema.title;
|
|
442
|
+
const uiSchema = {};
|
|
443
|
+
extractUiSchema(schema, uiSchema);
|
|
444
|
+
return { schema, uiSchema };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, {
|
|
448
|
+
content: description,
|
|
449
|
+
linkTarget: "_blank"
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
var fieldOverrides = /*#__PURE__*/Object.freeze({
|
|
453
|
+
__proto__: null,
|
|
454
|
+
DescriptionField: DescriptionField
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const Form = withTheme(Theme);
|
|
458
|
+
function getUiSchemasFromSteps(steps) {
|
|
459
|
+
const uiSchemas = [];
|
|
460
|
+
steps.forEach((step) => {
|
|
461
|
+
const schemaProps = step.schema.properties;
|
|
462
|
+
for (const key in schemaProps) {
|
|
463
|
+
if (schemaProps.hasOwnProperty(key)) {
|
|
464
|
+
const uiSchema = schemaProps[key];
|
|
465
|
+
uiSchema.name = key;
|
|
466
|
+
uiSchemas.push(uiSchema);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
return uiSchemas;
|
|
471
|
+
}
|
|
472
|
+
function getReviewData(formData, steps) {
|
|
473
|
+
const uiSchemas = getUiSchemasFromSteps(steps);
|
|
474
|
+
const reviewData = {};
|
|
475
|
+
for (const key in formData) {
|
|
476
|
+
if (formData.hasOwnProperty(key)) {
|
|
477
|
+
const uiSchema = uiSchemas.find((us) => us.name === key);
|
|
478
|
+
if (!uiSchema) {
|
|
479
|
+
reviewData[key] = formData[key];
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (uiSchema["ui:widget"] === "password") {
|
|
483
|
+
reviewData[key] = "******";
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (!uiSchema["ui:backstage"] || !uiSchema["ui:backstage"].review) {
|
|
487
|
+
reviewData[key] = formData[key];
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const review = uiSchema["ui:backstage"].review;
|
|
491
|
+
if (review.mask) {
|
|
492
|
+
reviewData[key] = review.mask;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (!review.show) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
reviewData[key] = formData[key];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return reviewData;
|
|
502
|
+
}
|
|
503
|
+
const MultistepJsonForm = (props) => {
|
|
504
|
+
const {
|
|
505
|
+
formData,
|
|
506
|
+
onChange,
|
|
507
|
+
onReset,
|
|
508
|
+
onFinish,
|
|
509
|
+
fields,
|
|
510
|
+
widgets,
|
|
511
|
+
finishButtonLabel
|
|
512
|
+
} = props;
|
|
513
|
+
const [activeStep, setActiveStep] = useState(0);
|
|
514
|
+
const [disableButtons, setDisableButtons] = useState(false);
|
|
515
|
+
const errorApi = useApi(errorApiRef);
|
|
516
|
+
const featureFlagApi = useApi(featureFlagsApiRef);
|
|
517
|
+
const featureFlagKey = "backstage:featureFlag";
|
|
518
|
+
const filterOutProperties = (step) => {
|
|
519
|
+
var _a;
|
|
520
|
+
const filteredStep = cloneDeep(step);
|
|
521
|
+
const removedPropertyKeys = [];
|
|
522
|
+
if (filteredStep.schema.properties) {
|
|
523
|
+
filteredStep.schema.properties = Object.fromEntries(Object.entries(filteredStep.schema.properties).filter(([key, value]) => {
|
|
524
|
+
if (value[featureFlagKey]) {
|
|
525
|
+
if (featureFlagApi.isActive(value[featureFlagKey])) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
removedPropertyKeys.push(key);
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
}));
|
|
533
|
+
filteredStep.schema.required = Array.isArray(filteredStep.schema.required) ? (_a = filteredStep.schema.required) == null ? void 0 : _a.filter((r) => !removedPropertyKeys.includes(r)) : filteredStep.schema.required;
|
|
534
|
+
}
|
|
535
|
+
return filteredStep;
|
|
536
|
+
};
|
|
537
|
+
const steps = props.steps.filter((step) => {
|
|
538
|
+
const featureFlag = step.schema[featureFlagKey];
|
|
539
|
+
return typeof featureFlag !== "string" || featureFlagApi.isActive(featureFlag);
|
|
540
|
+
}).map(filterOutProperties);
|
|
541
|
+
const handleReset = () => {
|
|
542
|
+
setActiveStep(0);
|
|
543
|
+
onReset();
|
|
544
|
+
};
|
|
545
|
+
const handleNext = () => {
|
|
546
|
+
setActiveStep(Math.min(activeStep + 1, steps.length));
|
|
547
|
+
};
|
|
548
|
+
const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
|
|
549
|
+
const handleCreate = async () => {
|
|
550
|
+
if (!onFinish) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
setDisableButtons(true);
|
|
554
|
+
try {
|
|
555
|
+
await onFinish();
|
|
556
|
+
} catch (err) {
|
|
557
|
+
errorApi.post(err);
|
|
558
|
+
} finally {
|
|
559
|
+
setDisableButtons(false);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, {
|
|
563
|
+
activeStep,
|
|
564
|
+
orientation: "vertical"
|
|
565
|
+
}, steps.map(({ title, schema, ...formProps }, index) => {
|
|
566
|
+
return /* @__PURE__ */ React.createElement(Step, {
|
|
567
|
+
key: title
|
|
568
|
+
}, /* @__PURE__ */ React.createElement(StepLabel, {
|
|
569
|
+
"aria-label": `Step ${index + 1} ${title}`,
|
|
570
|
+
"aria-disabled": "false",
|
|
571
|
+
tabIndex: 0
|
|
572
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
573
|
+
variant: "h6",
|
|
574
|
+
component: "h3"
|
|
575
|
+
}, title)), /* @__PURE__ */ React.createElement(StepContent, {
|
|
576
|
+
key: title
|
|
577
|
+
}, /* @__PURE__ */ React.createElement(Form, {
|
|
578
|
+
showErrorList: false,
|
|
579
|
+
fields: { ...fieldOverrides, ...fields },
|
|
580
|
+
widgets,
|
|
581
|
+
noHtml5Validate: true,
|
|
582
|
+
formData,
|
|
583
|
+
formContext: { formData },
|
|
584
|
+
onChange,
|
|
585
|
+
onSubmit: (e) => {
|
|
586
|
+
if (e.errors.length === 0)
|
|
587
|
+
handleNext();
|
|
588
|
+
},
|
|
589
|
+
...formProps,
|
|
590
|
+
...transformSchemaToProps(schema)
|
|
591
|
+
}, /* @__PURE__ */ React.createElement(Button$1, {
|
|
592
|
+
disabled: activeStep === 0,
|
|
593
|
+
onClick: handleBack
|
|
594
|
+
}, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
|
|
595
|
+
variant: "contained",
|
|
596
|
+
color: "primary",
|
|
597
|
+
type: "submit"
|
|
598
|
+
}, "Next step"))));
|
|
599
|
+
})), activeStep === steps.length && /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, {
|
|
600
|
+
square: true,
|
|
601
|
+
elevation: 0
|
|
602
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
603
|
+
variant: "h6"
|
|
604
|
+
}, "Review and create"), /* @__PURE__ */ React.createElement(StructuredMetadataTable, {
|
|
605
|
+
dense: true,
|
|
606
|
+
metadata: getReviewData(formData, steps)
|
|
607
|
+
}), /* @__PURE__ */ React.createElement(Box, {
|
|
608
|
+
mb: 4
|
|
609
|
+
}), /* @__PURE__ */ React.createElement(Button$1, {
|
|
610
|
+
onClick: handleBack,
|
|
611
|
+
disabled: disableButtons
|
|
612
|
+
}, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
|
|
613
|
+
onClick: handleReset,
|
|
614
|
+
disabled: disableButtons
|
|
615
|
+
}, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
|
|
616
|
+
variant: "contained",
|
|
617
|
+
color: "primary",
|
|
618
|
+
onClick: handleCreate,
|
|
619
|
+
disabled: !onFinish || disableButtons
|
|
620
|
+
}, finishButtonLabel != null ? finishButtonLabel : "Create"))));
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
function isObject(obj) {
|
|
624
|
+
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
|
625
|
+
}
|
|
626
|
+
const createValidator = (rootSchema, validators, context) => {
|
|
627
|
+
function validate(schema, formData, errors) {
|
|
628
|
+
const schemaProps = schema.properties;
|
|
629
|
+
const customObject = schema.type === "object" && schemaProps === void 0;
|
|
630
|
+
if (!isObject(schemaProps) && !customObject) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (schemaProps) {
|
|
634
|
+
for (const [key, propData] of Object.entries(formData)) {
|
|
635
|
+
const propValidation = errors[key];
|
|
636
|
+
if (isObject(propData)) {
|
|
637
|
+
const propSchemaProps = schemaProps[key];
|
|
638
|
+
if (isObject(propSchemaProps)) {
|
|
639
|
+
validate(propSchemaProps, propData, propValidation);
|
|
640
|
+
}
|
|
641
|
+
} else {
|
|
642
|
+
const propSchema = schemaProps[key];
|
|
643
|
+
const fieldName = isObject(propSchema) && propSchema["ui:field"];
|
|
644
|
+
if (fieldName && typeof validators[fieldName] === "function") {
|
|
645
|
+
validators[fieldName](propData, propValidation, context);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} else if (customObject) {
|
|
650
|
+
const fieldName = schema["ui:field"];
|
|
651
|
+
if (fieldName && typeof validators[fieldName] === "function") {
|
|
652
|
+
validators[fieldName](formData, errors, context);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return (formData, errors) => {
|
|
657
|
+
validate(rootSchema, formData, errors);
|
|
658
|
+
return errors;
|
|
659
|
+
};
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const useTemplateParameterSchema = (templateRef) => {
|
|
663
|
+
const scaffolderApi = useApi(scaffolderApiRef);
|
|
664
|
+
const { value, loading, error } = useAsync(() => scaffolderApi.getTemplateParameterSchema(templateRef), [scaffolderApi, templateRef]);
|
|
665
|
+
return { schema: value, loading, error };
|
|
666
|
+
};
|
|
667
|
+
const TemplatePage = ({
|
|
668
|
+
customFieldExtensions = []
|
|
669
|
+
}) => {
|
|
670
|
+
const apiHolder = useApiHolder();
|
|
671
|
+
const secretsContext = useContext(SecretsContext);
|
|
672
|
+
const errorApi = useApi(errorApiRef);
|
|
673
|
+
const scaffolderApi = useApi(scaffolderApiRef);
|
|
674
|
+
const { templateName } = useParams();
|
|
675
|
+
const navigate = useNavigate();
|
|
676
|
+
const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef);
|
|
677
|
+
const rootRoute = useRouteRef(rootRouteRef);
|
|
678
|
+
const { schema, loading, error } = useTemplateParameterSchema(templateName);
|
|
679
|
+
const [formState, setFormState] = useState(() => {
|
|
680
|
+
var _a;
|
|
681
|
+
const query = qs.parse(window.location.search, {
|
|
682
|
+
ignoreQueryPrefix: true
|
|
683
|
+
});
|
|
684
|
+
try {
|
|
685
|
+
return JSON.parse(query.formData);
|
|
686
|
+
} catch (e) {
|
|
687
|
+
return (_a = query.formData) != null ? _a : {};
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
const handleFormReset = () => setFormState({});
|
|
691
|
+
const handleChange = useCallback((e) => setFormState(e.formData), [setFormState]);
|
|
692
|
+
const handleCreate = async () => {
|
|
693
|
+
var _a;
|
|
694
|
+
const { taskId } = await scaffolderApi.scaffold({
|
|
695
|
+
templateRef: stringifyEntityRef({
|
|
696
|
+
name: templateName,
|
|
697
|
+
kind: "template",
|
|
698
|
+
namespace: "default"
|
|
699
|
+
}),
|
|
700
|
+
values: formState,
|
|
701
|
+
secrets: secretsContext == null ? void 0 : secretsContext.secrets
|
|
702
|
+
});
|
|
703
|
+
const formParams = qs.stringify({ formData: formState }, { addQueryPrefix: true });
|
|
704
|
+
const newUrl = `${window.location.pathname}${formParams}`;
|
|
705
|
+
(_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
|
|
706
|
+
navigate(scaffolderTaskRoute({ taskId }));
|
|
707
|
+
};
|
|
708
|
+
if (error) {
|
|
709
|
+
errorApi.post(new Error(`Failed to load template, ${error}`));
|
|
710
|
+
return /* @__PURE__ */ React.createElement(Navigate, {
|
|
711
|
+
to: rootRoute()
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (!loading && !schema) {
|
|
715
|
+
errorApi.post(new Error("Template was not found."));
|
|
716
|
+
return /* @__PURE__ */ React.createElement(Navigate, {
|
|
717
|
+
to: rootRoute()
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
|
|
721
|
+
const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
|
|
722
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
723
|
+
themeId: "home"
|
|
724
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
725
|
+
pageTitleOverride: "Create a New Component",
|
|
726
|
+
title: "Create a New Component",
|
|
727
|
+
subtitle: "Create new software components using standard templates"
|
|
728
|
+
}), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, {
|
|
729
|
+
"data-testid": "loading-progress"
|
|
730
|
+
}), schema && /* @__PURE__ */ React.createElement(InfoCard, {
|
|
731
|
+
title: schema.title,
|
|
732
|
+
noPadding: true,
|
|
733
|
+
titleTypographyProps: { component: "h2" }
|
|
734
|
+
}, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
|
|
735
|
+
formData: formState,
|
|
736
|
+
fields: customFieldComponents,
|
|
737
|
+
onChange: handleChange,
|
|
738
|
+
onReset: handleFormReset,
|
|
739
|
+
onFinish: handleCreate,
|
|
740
|
+
steps: schema.steps.map((step) => {
|
|
741
|
+
return {
|
|
742
|
+
...step,
|
|
743
|
+
validate: createValidator(step.schema, customFieldValidators, { apiHolder })
|
|
744
|
+
};
|
|
745
|
+
})
|
|
746
|
+
}))));
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const useStyles$c = makeStyles((theme) => ({
|
|
750
|
+
code: {
|
|
751
|
+
fontFamily: "Menlo, monospace",
|
|
752
|
+
padding: theme.spacing(1),
|
|
753
|
+
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[700] : theme.palette.grey[300],
|
|
754
|
+
display: "inline-block",
|
|
755
|
+
borderRadius: 5,
|
|
756
|
+
border: `1px solid ${theme.palette.grey[500]}`,
|
|
757
|
+
position: "relative"
|
|
758
|
+
},
|
|
759
|
+
codeRequired: {
|
|
760
|
+
"&::after": {
|
|
761
|
+
position: "absolute",
|
|
762
|
+
content: '"*"',
|
|
763
|
+
top: 0,
|
|
764
|
+
right: theme.spacing(0.5),
|
|
765
|
+
fontWeight: "bolder",
|
|
766
|
+
color: theme.palette.error.light
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}));
|
|
770
|
+
const ActionsPage = () => {
|
|
771
|
+
const api = useApi(scaffolderApiRef);
|
|
772
|
+
const classes = useStyles$c();
|
|
773
|
+
const { loading, value, error } = useAsync(async () => {
|
|
774
|
+
return api.listActions();
|
|
775
|
+
});
|
|
776
|
+
if (loading) {
|
|
777
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
778
|
+
}
|
|
779
|
+
if (error) {
|
|
780
|
+
return /* @__PURE__ */ React.createElement(ErrorPage, {
|
|
781
|
+
statusMessage: "Failed to load installed actions",
|
|
782
|
+
status: "500"
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const formatRows = (input) => {
|
|
786
|
+
const properties = input.properties;
|
|
787
|
+
if (!properties) {
|
|
788
|
+
return void 0;
|
|
789
|
+
}
|
|
790
|
+
return Object.entries(properties).map((entry) => {
|
|
791
|
+
var _a;
|
|
792
|
+
const [key] = entry;
|
|
793
|
+
const props = entry[1];
|
|
794
|
+
const codeClassname = classNames(classes.code, {
|
|
795
|
+
[classes.codeRequired]: (_a = input.required) == null ? void 0 : _a.includes(key)
|
|
796
|
+
});
|
|
797
|
+
return /* @__PURE__ */ React.createElement(TableRow, {
|
|
798
|
+
key
|
|
799
|
+
}, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("div", {
|
|
800
|
+
className: codeClassname
|
|
801
|
+
}, key)), /* @__PURE__ */ React.createElement(TableCell, null, props.title), /* @__PURE__ */ React.createElement(TableCell, null, props.description), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("span", {
|
|
802
|
+
className: classes.code
|
|
803
|
+
}, props.type)));
|
|
804
|
+
});
|
|
805
|
+
};
|
|
806
|
+
const renderTable = (input) => {
|
|
807
|
+
if (!input.properties) {
|
|
808
|
+
return void 0;
|
|
809
|
+
}
|
|
810
|
+
return /* @__PURE__ */ React.createElement(TableContainer, {
|
|
811
|
+
component: Paper
|
|
812
|
+
}, /* @__PURE__ */ React.createElement(Table, {
|
|
813
|
+
size: "small"
|
|
814
|
+
}, /* @__PURE__ */ React.createElement(TableHead, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableCell, null, "Name"), /* @__PURE__ */ React.createElement(TableCell, null, "Title"), /* @__PURE__ */ React.createElement(TableCell, null, "Description"), /* @__PURE__ */ React.createElement(TableCell, null, "Type"))), /* @__PURE__ */ React.createElement(TableBody, null, formatRows(input))));
|
|
815
|
+
};
|
|
816
|
+
const renderTables = (name, input) => {
|
|
817
|
+
if (!input) {
|
|
818
|
+
return void 0;
|
|
819
|
+
}
|
|
820
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, {
|
|
821
|
+
variant: "h6"
|
|
822
|
+
}, name), input.map((i, index) => /* @__PURE__ */ React.createElement("div", {
|
|
823
|
+
key: index
|
|
824
|
+
}, renderTable(i))));
|
|
825
|
+
};
|
|
826
|
+
const items = value == null ? void 0 : value.map((action) => {
|
|
827
|
+
var _a, _b, _c, _d;
|
|
828
|
+
if (action.id.startsWith("legacy:")) {
|
|
829
|
+
return void 0;
|
|
830
|
+
}
|
|
831
|
+
const oneOf = renderTables("oneOf", (_b = (_a = action.schema) == null ? void 0 : _a.input) == null ? void 0 : _b.oneOf);
|
|
832
|
+
return /* @__PURE__ */ React.createElement(Box, {
|
|
833
|
+
pb: 4,
|
|
834
|
+
key: action.id
|
|
835
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
836
|
+
variant: "h4",
|
|
837
|
+
className: classes.code
|
|
838
|
+
}, action.id), /* @__PURE__ */ React.createElement(Typography, null, action.description), ((_c = action.schema) == null ? void 0 : _c.input) && /* @__PURE__ */ React.createElement(Box, {
|
|
839
|
+
pb: 2
|
|
840
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
841
|
+
variant: "h5"
|
|
842
|
+
}, "Input"), renderTable(action.schema.input), oneOf), ((_d = action.schema) == null ? void 0 : _d.output) && /* @__PURE__ */ React.createElement(Box, {
|
|
843
|
+
pb: 2
|
|
844
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
845
|
+
variant: "h5"
|
|
846
|
+
}, "Output"), renderTable(action.schema.output)));
|
|
847
|
+
});
|
|
848
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
849
|
+
themeId: "home"
|
|
850
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
851
|
+
pageTitleOverride: "Create a New Component",
|
|
852
|
+
title: "Installed actions",
|
|
853
|
+
subtitle: "This is the collection of all installed actions"
|
|
854
|
+
}), /* @__PURE__ */ React.createElement(Content, null, items));
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const showDirectoryPicker = window.showDirectoryPicker;
|
|
858
|
+
class WebFileAccess {
|
|
859
|
+
constructor(path, handle) {
|
|
860
|
+
this.path = path;
|
|
861
|
+
this.handle = handle;
|
|
862
|
+
}
|
|
863
|
+
file() {
|
|
864
|
+
return this.handle.getFile();
|
|
865
|
+
}
|
|
866
|
+
async save(data) {
|
|
867
|
+
const writable = await this.handle.createWritable();
|
|
868
|
+
await writable.write(data);
|
|
869
|
+
await writable.close();
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
class WebDirectoryAccess {
|
|
873
|
+
constructor(handle) {
|
|
874
|
+
this.handle = handle;
|
|
875
|
+
}
|
|
876
|
+
async listFiles() {
|
|
877
|
+
const content = [];
|
|
878
|
+
for await (const entry of this.listDirectoryContents(this.handle)) {
|
|
879
|
+
content.push(entry);
|
|
880
|
+
}
|
|
881
|
+
return content;
|
|
882
|
+
}
|
|
883
|
+
async *listDirectoryContents(dirHandle, basePath = []) {
|
|
884
|
+
for await (const handle of dirHandle.values()) {
|
|
885
|
+
if (handle.kind === "file") {
|
|
886
|
+
yield new WebFileAccess([...basePath, handle.name].join("/"), handle);
|
|
887
|
+
} else if (handle.kind === "directory") {
|
|
888
|
+
yield* this.listDirectoryContents(handle, [...basePath, handle.name]);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
class WebFileSystemAccess {
|
|
894
|
+
static isSupported() {
|
|
895
|
+
return Boolean(showDirectoryPicker);
|
|
896
|
+
}
|
|
897
|
+
static async requestDirectoryAccess() {
|
|
898
|
+
if (!showDirectoryPicker) {
|
|
899
|
+
throw new Error("File system access is not supported");
|
|
900
|
+
}
|
|
901
|
+
const handle = await showDirectoryPicker();
|
|
902
|
+
return new WebDirectoryAccess(handle);
|
|
903
|
+
}
|
|
904
|
+
constructor() {
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const useStyles$b = makeStyles$1((theme) => ({
|
|
909
|
+
introText: {
|
|
910
|
+
textAlign: "center",
|
|
911
|
+
marginTop: theme.spacing(2)
|
|
912
|
+
},
|
|
913
|
+
card: {
|
|
914
|
+
position: "relative",
|
|
915
|
+
maxWidth: 340,
|
|
916
|
+
marginTop: theme.spacing(4),
|
|
917
|
+
margin: theme.spacing(0, 2)
|
|
918
|
+
},
|
|
919
|
+
infoIcon: {
|
|
920
|
+
position: "absolute",
|
|
921
|
+
top: theme.spacing(1),
|
|
922
|
+
right: theme.spacing(1)
|
|
923
|
+
}
|
|
924
|
+
}));
|
|
925
|
+
function TemplateEditorIntro(props) {
|
|
926
|
+
const classes = useStyles$b();
|
|
927
|
+
const supportsLoad = WebFileSystemAccess.isSupported();
|
|
928
|
+
const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, {
|
|
929
|
+
className: classes.card,
|
|
930
|
+
elevation: 4
|
|
931
|
+
}, /* @__PURE__ */ React.createElement(CardActionArea, {
|
|
932
|
+
disabled: !supportsLoad,
|
|
933
|
+
onClick: () => {
|
|
934
|
+
var _a;
|
|
935
|
+
return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
|
|
936
|
+
}
|
|
937
|
+
}, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
|
|
938
|
+
variant: "h5",
|
|
939
|
+
gutterBottom: true,
|
|
940
|
+
color: supportsLoad ? void 0 : "textSecondary",
|
|
941
|
+
style: { display: "flex", flexFlow: "row nowrap" }
|
|
942
|
+
}, "Load Template Directory"), /* @__PURE__ */ React.createElement(Typography$1, {
|
|
943
|
+
variant: "body1",
|
|
944
|
+
color: supportsLoad ? void 0 : "textSecondary"
|
|
945
|
+
}, "Load a local template directory, allowing you to both edit and try executing your own template."))), !supportsLoad && /* @__PURE__ */ React.createElement("div", {
|
|
946
|
+
className: classes.infoIcon
|
|
947
|
+
}, /* @__PURE__ */ React.createElement(Tooltip$1, {
|
|
948
|
+
placement: "top",
|
|
949
|
+
title: "Only supported in some Chromium-based browsers"
|
|
950
|
+
}, /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null))));
|
|
951
|
+
const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, {
|
|
952
|
+
className: classes.card,
|
|
953
|
+
elevation: 4
|
|
954
|
+
}, /* @__PURE__ */ React.createElement(CardActionArea, {
|
|
955
|
+
onClick: () => {
|
|
956
|
+
var _a;
|
|
957
|
+
return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
|
|
958
|
+
}
|
|
959
|
+
}, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
|
|
960
|
+
variant: "h5",
|
|
961
|
+
gutterBottom: true
|
|
962
|
+
}, "Edit Template Form"), /* @__PURE__ */ React.createElement(Typography$1, {
|
|
963
|
+
variant: "body1"
|
|
964
|
+
}, "Preview and edit a template form, either using a sample template or by loading a template from the catalog."))));
|
|
965
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
966
|
+
style: props.style
|
|
967
|
+
}, /* @__PURE__ */ React.createElement(Typography$1, {
|
|
968
|
+
variant: "h6",
|
|
969
|
+
className: classes.introText
|
|
970
|
+
}, "Get started by choosing one of the options below"), /* @__PURE__ */ React.createElement("div", {
|
|
971
|
+
style: {
|
|
972
|
+
display: "flex",
|
|
973
|
+
flexFlow: "row wrap",
|
|
974
|
+
alignItems: "flex-start",
|
|
975
|
+
justifyContent: "center",
|
|
976
|
+
alignContent: "flex-start"
|
|
977
|
+
}
|
|
978
|
+
}, supportsLoad && cardLoadLocal, cardFormEditor, !supportsLoad && cardLoadLocal));
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
var __accessCheck = (obj, member, msg) => {
|
|
982
|
+
if (!member.has(obj))
|
|
983
|
+
throw TypeError("Cannot " + msg);
|
|
984
|
+
};
|
|
985
|
+
var __privateGet = (obj, member, getter) => {
|
|
986
|
+
__accessCheck(obj, member, "read from private field");
|
|
987
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
988
|
+
};
|
|
989
|
+
var __privateAdd = (obj, member, value) => {
|
|
990
|
+
if (member.has(obj))
|
|
991
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
992
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
993
|
+
};
|
|
994
|
+
var __privateSet = (obj, member, value, setter) => {
|
|
995
|
+
__accessCheck(obj, member, "write to private field");
|
|
996
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
997
|
+
return value;
|
|
998
|
+
};
|
|
999
|
+
var _access, _signalUpdate, _content, _savedContent, _access2, _listeners, _files, _selectedFile, _signalUpdate2;
|
|
1000
|
+
const MAX_SIZE = 1024 * 1024;
|
|
1001
|
+
const MAX_SIZE_MESSAGE = "This file is too large to be displayed";
|
|
1002
|
+
class DirectoryEditorFileManager {
|
|
1003
|
+
constructor(access, signalUpdate) {
|
|
1004
|
+
__privateAdd(this, _access, void 0);
|
|
1005
|
+
__privateAdd(this, _signalUpdate, void 0);
|
|
1006
|
+
__privateAdd(this, _content, void 0);
|
|
1007
|
+
__privateAdd(this, _savedContent, void 0);
|
|
1008
|
+
__privateSet(this, _access, access);
|
|
1009
|
+
__privateSet(this, _signalUpdate, signalUpdate);
|
|
1010
|
+
}
|
|
1011
|
+
get path() {
|
|
1012
|
+
return __privateGet(this, _access).path;
|
|
1013
|
+
}
|
|
1014
|
+
get content() {
|
|
1015
|
+
var _a;
|
|
1016
|
+
return (_a = __privateGet(this, _content)) != null ? _a : MAX_SIZE_MESSAGE;
|
|
1017
|
+
}
|
|
1018
|
+
updateContent(content) {
|
|
1019
|
+
if (__privateGet(this, _content) === void 0) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
__privateSet(this, _content, content);
|
|
1023
|
+
__privateGet(this, _signalUpdate).call(this);
|
|
1024
|
+
}
|
|
1025
|
+
get dirty() {
|
|
1026
|
+
return __privateGet(this, _content) !== __privateGet(this, _savedContent);
|
|
1027
|
+
}
|
|
1028
|
+
async save() {
|
|
1029
|
+
if (__privateGet(this, _content) !== void 0) {
|
|
1030
|
+
await __privateGet(this, _access).save(__privateGet(this, _content));
|
|
1031
|
+
__privateSet(this, _savedContent, __privateGet(this, _content));
|
|
1032
|
+
__privateGet(this, _signalUpdate).call(this);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async reload() {
|
|
1036
|
+
const file = await __privateGet(this, _access).file();
|
|
1037
|
+
if (file.size > MAX_SIZE) {
|
|
1038
|
+
if (__privateGet(this, _content) !== void 0) {
|
|
1039
|
+
__privateSet(this, _content, void 0);
|
|
1040
|
+
__privateSet(this, _savedContent, void 0);
|
|
1041
|
+
__privateGet(this, _signalUpdate).call(this);
|
|
1042
|
+
}
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const content = await file.text();
|
|
1046
|
+
if (__privateGet(this, _content) !== content) {
|
|
1047
|
+
__privateSet(this, _content, content);
|
|
1048
|
+
__privateSet(this, _savedContent, content);
|
|
1049
|
+
__privateGet(this, _signalUpdate).call(this);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
_access = new WeakMap();
|
|
1054
|
+
_signalUpdate = new WeakMap();
|
|
1055
|
+
_content = new WeakMap();
|
|
1056
|
+
_savedContent = new WeakMap();
|
|
1057
|
+
class DirectoryEditorManager {
|
|
1058
|
+
constructor(access) {
|
|
1059
|
+
__privateAdd(this, _access2, void 0);
|
|
1060
|
+
__privateAdd(this, _listeners, /* @__PURE__ */ new Set());
|
|
1061
|
+
__privateAdd(this, _files, []);
|
|
1062
|
+
__privateAdd(this, _selectedFile, void 0);
|
|
1063
|
+
this.setSelectedFile = (path) => {
|
|
1064
|
+
const prev = __privateGet(this, _selectedFile);
|
|
1065
|
+
const next = __privateGet(this, _files).find((file) => file.path === path);
|
|
1066
|
+
if (prev !== next) {
|
|
1067
|
+
__privateSet(this, _selectedFile, next);
|
|
1068
|
+
__privateGet(this, _signalUpdate2).call(this);
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
__privateAdd(this, _signalUpdate2, () => {
|
|
1072
|
+
__privateGet(this, _listeners).forEach((listener) => listener());
|
|
1073
|
+
});
|
|
1074
|
+
__privateSet(this, _access2, access);
|
|
1075
|
+
}
|
|
1076
|
+
get files() {
|
|
1077
|
+
return __privateGet(this, _files);
|
|
1078
|
+
}
|
|
1079
|
+
get selectedFile() {
|
|
1080
|
+
return __privateGet(this, _selectedFile);
|
|
1081
|
+
}
|
|
1082
|
+
get dirty() {
|
|
1083
|
+
return __privateGet(this, _files).some((file) => file.dirty);
|
|
1084
|
+
}
|
|
1085
|
+
async save() {
|
|
1086
|
+
await Promise.all(__privateGet(this, _files).map((file) => file.save()));
|
|
1087
|
+
}
|
|
1088
|
+
async reload() {
|
|
1089
|
+
var _a;
|
|
1090
|
+
const selectedPath = (_a = __privateGet(this, _selectedFile)) == null ? void 0 : _a.path;
|
|
1091
|
+
const files = await __privateGet(this, _access2).listFiles();
|
|
1092
|
+
const fileManagers = await Promise.all(files.map(async (file) => {
|
|
1093
|
+
const manager = new DirectoryEditorFileManager(file, __privateGet(this, _signalUpdate2));
|
|
1094
|
+
await manager.reload();
|
|
1095
|
+
return manager;
|
|
1096
|
+
}));
|
|
1097
|
+
__privateGet(this, _files).length = 0;
|
|
1098
|
+
__privateGet(this, _files).push(...fileManagers);
|
|
1099
|
+
this.setSelectedFile(selectedPath);
|
|
1100
|
+
__privateGet(this, _signalUpdate2).call(this);
|
|
1101
|
+
}
|
|
1102
|
+
subscribe(listener) {
|
|
1103
|
+
__privateGet(this, _listeners).add(listener);
|
|
1104
|
+
return () => {
|
|
1105
|
+
__privateGet(this, _listeners).delete(listener);
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
_access2 = new WeakMap();
|
|
1110
|
+
_listeners = new WeakMap();
|
|
1111
|
+
_files = new WeakMap();
|
|
1112
|
+
_selectedFile = new WeakMap();
|
|
1113
|
+
_signalUpdate2 = new WeakMap();
|
|
1114
|
+
const DirectoryEditorContext = createContext(void 0);
|
|
1115
|
+
function useDirectoryEditor() {
|
|
1116
|
+
const value = useContext(DirectoryEditorContext);
|
|
1117
|
+
const rerender = useRerender();
|
|
1118
|
+
useEffect(() => value == null ? void 0 : value.subscribe(rerender), [value, rerender]);
|
|
1119
|
+
if (!value) {
|
|
1120
|
+
throw new Error("must be used within a DirectoryEditorProvider");
|
|
1121
|
+
}
|
|
1122
|
+
return value;
|
|
1123
|
+
}
|
|
1124
|
+
function DirectoryEditorProvider(props) {
|
|
1125
|
+
const { directory } = props;
|
|
1126
|
+
const [{ result, error }, { execute }] = useAsync$1(async (dir) => {
|
|
1127
|
+
const manager = new DirectoryEditorManager(dir);
|
|
1128
|
+
await manager.reload();
|
|
1129
|
+
const firstYaml = manager.files.find((file) => file.path.match(/\.ya?ml$/));
|
|
1130
|
+
if (firstYaml) {
|
|
1131
|
+
manager.setSelectedFile(firstYaml.path);
|
|
1132
|
+
}
|
|
1133
|
+
return manager;
|
|
1134
|
+
});
|
|
1135
|
+
useEffect(() => {
|
|
1136
|
+
execute(directory);
|
|
1137
|
+
}, [execute, directory]);
|
|
1138
|
+
if (error) {
|
|
1139
|
+
return /* @__PURE__ */ React.createElement(ErrorPanel, {
|
|
1140
|
+
error
|
|
1141
|
+
});
|
|
1142
|
+
} else if (!result) {
|
|
1143
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
1144
|
+
}
|
|
1145
|
+
return /* @__PURE__ */ React.createElement(DirectoryEditorContext.Provider, {
|
|
1146
|
+
value: result
|
|
1147
|
+
}, props.children);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const MAX_CONTENT_SIZE = 256 * 1024;
|
|
1151
|
+
const CHUNK_SIZE = 32768;
|
|
1152
|
+
const DryRunContext = createContext(void 0);
|
|
1153
|
+
function base64EncodeContent(content) {
|
|
1154
|
+
if (content.length > MAX_CONTENT_SIZE) {
|
|
1155
|
+
return btoa("<file too large>");
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
return btoa(content);
|
|
1159
|
+
} catch {
|
|
1160
|
+
const decoder = new TextEncoder();
|
|
1161
|
+
const buffer = decoder.encode(content);
|
|
1162
|
+
const chunks = new Array();
|
|
1163
|
+
for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
|
|
1164
|
+
chunks.push(String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE)));
|
|
1165
|
+
}
|
|
1166
|
+
return btoa(chunks.join(""));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function DryRunProvider(props) {
|
|
1170
|
+
const scaffolderApi = useApi(scaffolderApiRef);
|
|
1171
|
+
const [state, setState] = useState({
|
|
1172
|
+
results: [],
|
|
1173
|
+
selectedResult: void 0
|
|
1174
|
+
});
|
|
1175
|
+
const idRef = useRef(1);
|
|
1176
|
+
const selectResult = useCallback((id) => {
|
|
1177
|
+
setState((prevState) => {
|
|
1178
|
+
const result = prevState.results.find((r) => r.id === id);
|
|
1179
|
+
if (result === prevState.selectedResult) {
|
|
1180
|
+
return prevState;
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
results: prevState.results,
|
|
1184
|
+
selectedResult: result
|
|
1185
|
+
};
|
|
1186
|
+
});
|
|
1187
|
+
}, []);
|
|
1188
|
+
const deleteResult = useCallback((id) => {
|
|
1189
|
+
setState((prevState) => {
|
|
1190
|
+
var _a;
|
|
1191
|
+
const index = prevState.results.findIndex((r) => r.id === id);
|
|
1192
|
+
if (index === -1) {
|
|
1193
|
+
return prevState;
|
|
1194
|
+
}
|
|
1195
|
+
const newResults = prevState.results.slice();
|
|
1196
|
+
const [deleted] = newResults.splice(index, 1);
|
|
1197
|
+
return {
|
|
1198
|
+
results: newResults,
|
|
1199
|
+
selectedResult: ((_a = prevState.selectedResult) == null ? void 0 : _a.id) === deleted.id ? newResults[0] : prevState.selectedResult
|
|
1200
|
+
};
|
|
1201
|
+
});
|
|
1202
|
+
}, []);
|
|
1203
|
+
const execute = useCallback(async (options) => {
|
|
1204
|
+
if (!scaffolderApi.dryRun) {
|
|
1205
|
+
throw new Error("Scaffolder API does not support dry-run");
|
|
1206
|
+
}
|
|
1207
|
+
const parsed = yaml.parse(options.templateContent);
|
|
1208
|
+
const response = await scaffolderApi.dryRun({
|
|
1209
|
+
template: parsed,
|
|
1210
|
+
values: options.values,
|
|
1211
|
+
secrets: {},
|
|
1212
|
+
directoryContents: options.files.map((file) => ({
|
|
1213
|
+
path: file.path,
|
|
1214
|
+
base64Content: base64EncodeContent(file.content)
|
|
1215
|
+
}))
|
|
1216
|
+
});
|
|
1217
|
+
const result = {
|
|
1218
|
+
...response,
|
|
1219
|
+
id: idRef.current++
|
|
1220
|
+
};
|
|
1221
|
+
setState((prevState) => {
|
|
1222
|
+
var _a;
|
|
1223
|
+
return {
|
|
1224
|
+
results: [...prevState.results, result],
|
|
1225
|
+
selectedResult: (_a = prevState.selectedResult) != null ? _a : result
|
|
1226
|
+
};
|
|
1227
|
+
});
|
|
1228
|
+
}, [scaffolderApi]);
|
|
1229
|
+
const dryRun = useMemo(() => ({
|
|
1230
|
+
...state,
|
|
1231
|
+
selectResult,
|
|
1232
|
+
deleteResult,
|
|
1233
|
+
execute
|
|
1234
|
+
}), [state, selectResult, deleteResult, execute]);
|
|
1235
|
+
return /* @__PURE__ */ React.createElement(DryRunContext.Provider, {
|
|
1236
|
+
value: dryRun
|
|
1237
|
+
}, props.children);
|
|
1238
|
+
}
|
|
1239
|
+
function useDryRun() {
|
|
1240
|
+
const value = useContext(DryRunContext);
|
|
1241
|
+
if (!value) {
|
|
1242
|
+
throw new Error("must be used within a DryRunProvider");
|
|
1243
|
+
}
|
|
1244
|
+
return value;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const useStyles$a = makeStyles$1((theme) => ({
|
|
1248
|
+
root: {
|
|
1249
|
+
overflowY: "auto",
|
|
1250
|
+
background: theme.palette.background.default
|
|
1251
|
+
},
|
|
1252
|
+
iconSuccess: {
|
|
1253
|
+
minWidth: 0,
|
|
1254
|
+
marginRight: theme.spacing(1),
|
|
1255
|
+
color: theme.palette.status.ok
|
|
1256
|
+
},
|
|
1257
|
+
iconFailure: {
|
|
1258
|
+
minWidth: 0,
|
|
1259
|
+
marginRight: theme.spacing(1),
|
|
1260
|
+
color: theme.palette.status.error
|
|
1261
|
+
}
|
|
1262
|
+
}));
|
|
1263
|
+
function DryRunResultsList() {
|
|
1264
|
+
const classes = useStyles$a();
|
|
1265
|
+
const dryRun = useDryRun();
|
|
1266
|
+
return /* @__PURE__ */ React.createElement(List$1, {
|
|
1267
|
+
className: classes.root,
|
|
1268
|
+
dense: true
|
|
1269
|
+
}, dryRun.results.map((result) => {
|
|
1270
|
+
var _a;
|
|
1271
|
+
const failed = result.log.some((l) => l.body.status === "failed");
|
|
1272
|
+
return /* @__PURE__ */ React.createElement(ListItem, {
|
|
1273
|
+
button: true,
|
|
1274
|
+
key: result.id,
|
|
1275
|
+
selected: ((_a = dryRun.selectedResult) == null ? void 0 : _a.id) === result.id,
|
|
1276
|
+
onClick: () => dryRun.selectResult(result.id)
|
|
1277
|
+
}, /* @__PURE__ */ React.createElement(ListItemIcon, {
|
|
1278
|
+
className: failed ? classes.iconFailure : classes.iconSuccess
|
|
1279
|
+
}, failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(Check, null)), /* @__PURE__ */ React.createElement(ListItemText, {
|
|
1280
|
+
primary: `Result ${result.id}`
|
|
1281
|
+
}), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(IconButton$1, {
|
|
1282
|
+
edge: "end",
|
|
1283
|
+
"aria-label": "delete",
|
|
1284
|
+
onClick: () => dryRun.deleteResult(result.id)
|
|
1285
|
+
}, /* @__PURE__ */ React.createElement(DeleteIcon, null))));
|
|
1286
|
+
}));
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const useStyles$9 = makeStyles$1({
|
|
1290
|
+
root: {
|
|
1291
|
+
whiteSpace: "nowrap",
|
|
1292
|
+
overflowY: "auto"
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
function parseFileEntires(paths) {
|
|
1296
|
+
const root = {
|
|
1297
|
+
type: "directory",
|
|
1298
|
+
name: "",
|
|
1299
|
+
path: "",
|
|
1300
|
+
children: []
|
|
1301
|
+
};
|
|
1302
|
+
for (const path of paths.slice().sort()) {
|
|
1303
|
+
const parts = path.split("/");
|
|
1304
|
+
let current = root;
|
|
1305
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1306
|
+
const part = parts[i];
|
|
1307
|
+
if (part === "") {
|
|
1308
|
+
throw new Error(`Invalid path part: ''`);
|
|
1309
|
+
}
|
|
1310
|
+
const entryPath = parts.slice(0, i + 1).join("/");
|
|
1311
|
+
const existing = current.children.find((child) => child.name === part);
|
|
1312
|
+
if ((existing == null ? void 0 : existing.type) === "file") {
|
|
1313
|
+
throw new Error(`Duplicate filename at '${entryPath}'`);
|
|
1314
|
+
} else if (existing) {
|
|
1315
|
+
current = existing;
|
|
1316
|
+
} else {
|
|
1317
|
+
if (i < parts.length - 1) {
|
|
1318
|
+
const newEntry = {
|
|
1319
|
+
type: "directory",
|
|
1320
|
+
name: part,
|
|
1321
|
+
path: entryPath,
|
|
1322
|
+
children: []
|
|
1323
|
+
};
|
|
1324
|
+
const firstFileIndex = current.children.findIndex((child) => child.type === "file");
|
|
1325
|
+
current.children.splice(firstFileIndex, 0, newEntry);
|
|
1326
|
+
current = newEntry;
|
|
1327
|
+
} else {
|
|
1328
|
+
current.children.push({
|
|
1329
|
+
type: "file",
|
|
1330
|
+
name: part,
|
|
1331
|
+
path: entryPath
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return root.children;
|
|
1338
|
+
}
|
|
1339
|
+
function FileTreeItem({ entry }) {
|
|
1340
|
+
if (entry.type === "file") {
|
|
1341
|
+
return /* @__PURE__ */ React.createElement(TreeItem, {
|
|
1342
|
+
nodeId: entry.path,
|
|
1343
|
+
label: entry.name
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
return /* @__PURE__ */ React.createElement(TreeItem, {
|
|
1347
|
+
nodeId: entry.path,
|
|
1348
|
+
label: entry.name
|
|
1349
|
+
}, entry.children.map((child) => /* @__PURE__ */ React.createElement(FileTreeItem, {
|
|
1350
|
+
key: child.path,
|
|
1351
|
+
entry: child
|
|
1352
|
+
})));
|
|
1353
|
+
}
|
|
1354
|
+
function FileBrowser(props) {
|
|
1355
|
+
const classes = useStyles$9();
|
|
1356
|
+
const fileTree = useMemo(() => parseFileEntires(props.filePaths), [props.filePaths]);
|
|
1357
|
+
return /* @__PURE__ */ React.createElement(TreeView, {
|
|
1358
|
+
selected: props.selected,
|
|
1359
|
+
className: classes.root,
|
|
1360
|
+
defaultCollapseIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null),
|
|
1361
|
+
defaultExpandIcon: /* @__PURE__ */ React.createElement(ChevronRightIcon, null),
|
|
1362
|
+
onNodeSelect: (_e, nodeId) => {
|
|
1363
|
+
if (props.onSelect && props.filePaths.includes(nodeId)) {
|
|
1364
|
+
props.onSelect(nodeId);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}, fileTree.map((entry) => /* @__PURE__ */ React.createElement(FileTreeItem, {
|
|
1368
|
+
key: entry.path,
|
|
1369
|
+
entry
|
|
1370
|
+
})));
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const useStyles$8 = makeStyles$1((theme) => ({
|
|
1374
|
+
root: {
|
|
1375
|
+
display: "grid",
|
|
1376
|
+
gridTemplateColumns: "280px auto 3fr",
|
|
1377
|
+
gridTemplateRows: "1fr"
|
|
1378
|
+
},
|
|
1379
|
+
child: {
|
|
1380
|
+
overflowY: "auto",
|
|
1381
|
+
height: "100%",
|
|
1382
|
+
minHeight: 0
|
|
1383
|
+
},
|
|
1384
|
+
firstChild: {
|
|
1385
|
+
background: theme.palette.background.paper
|
|
1386
|
+
}
|
|
1387
|
+
}));
|
|
1388
|
+
function DryRunResultsSplitView(props) {
|
|
1389
|
+
const classes = useStyles$8();
|
|
1390
|
+
const childArray = Children.toArray(props.children);
|
|
1391
|
+
if (childArray.length !== 2) {
|
|
1392
|
+
throw new Error("must have exactly 2 children");
|
|
1393
|
+
}
|
|
1394
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1395
|
+
className: classes.root
|
|
1396
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
1397
|
+
className: classNames(classes.child, classes.firstChild)
|
|
1398
|
+
}, childArray[0]), /* @__PURE__ */ React.createElement(Divider, {
|
|
1399
|
+
orientation: "horizontal"
|
|
1400
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
1401
|
+
className: classes.child
|
|
1402
|
+
}, childArray[1]));
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
const useStyles$7 = makeStyles$1({
|
|
1406
|
+
root: {
|
|
1407
|
+
display: "flex",
|
|
1408
|
+
flexFlow: "column nowrap"
|
|
1409
|
+
},
|
|
1410
|
+
contentWrapper: {
|
|
1411
|
+
flex: 1,
|
|
1412
|
+
position: "relative"
|
|
1413
|
+
},
|
|
1414
|
+
content: {
|
|
1415
|
+
position: "absolute",
|
|
1416
|
+
top: 0,
|
|
1417
|
+
left: 0,
|
|
1418
|
+
right: 0,
|
|
1419
|
+
bottom: 0,
|
|
1420
|
+
display: "flex",
|
|
1421
|
+
"& > *": {
|
|
1422
|
+
flex: 1
|
|
1423
|
+
}
|
|
1424
|
+
},
|
|
1425
|
+
codeMirror: {
|
|
1426
|
+
height: "100%",
|
|
1427
|
+
overflowY: "auto"
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
function FilesContent() {
|
|
1431
|
+
const classes = useStyles$7();
|
|
1432
|
+
const { selectedResult } = useDryRun();
|
|
1433
|
+
const [selectedPath, setSelectedPath] = useState("");
|
|
1434
|
+
const selectedFile = selectedResult == null ? void 0 : selectedResult.directoryContents.find((f) => f.path === selectedPath);
|
|
1435
|
+
useEffect(() => {
|
|
1436
|
+
if (selectedResult) {
|
|
1437
|
+
const [firstFile] = selectedResult.directoryContents;
|
|
1438
|
+
if (firstFile) {
|
|
1439
|
+
setSelectedPath(firstFile.path);
|
|
1440
|
+
} else {
|
|
1441
|
+
setSelectedPath("");
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}, [selectedResult]);
|
|
1446
|
+
if (!selectedResult) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
return /* @__PURE__ */ React.createElement(DryRunResultsSplitView, null, /* @__PURE__ */ React.createElement(FileBrowser, {
|
|
1450
|
+
selected: selectedPath,
|
|
1451
|
+
onSelect: setSelectedPath,
|
|
1452
|
+
filePaths: selectedResult.directoryContents.map((file) => file.path)
|
|
1453
|
+
}), /* @__PURE__ */ React.createElement(CodeMirror, {
|
|
1454
|
+
className: classes.codeMirror,
|
|
1455
|
+
theme: "dark",
|
|
1456
|
+
height: "100%",
|
|
1457
|
+
extensions: [StreamLanguage.define(yaml$1)],
|
|
1458
|
+
readOnly: true,
|
|
1459
|
+
value: (selectedFile == null ? void 0 : selectedFile.base64Content) ? atob(selectedFile.base64Content) : ""
|
|
1460
|
+
}));
|
|
1461
|
+
}
|
|
1462
|
+
function LogContent() {
|
|
1463
|
+
var _a, _b;
|
|
1464
|
+
const { selectedResult } = useDryRun();
|
|
1465
|
+
const [currentStepId, setUserSelectedStepId] = useState();
|
|
1466
|
+
const steps = useMemo(() => {
|
|
1467
|
+
var _a2;
|
|
1468
|
+
if (!selectedResult) {
|
|
1469
|
+
return [];
|
|
1470
|
+
}
|
|
1471
|
+
return (_a2 = selectedResult.steps.map((step) => {
|
|
1472
|
+
var _a3, _b2;
|
|
1473
|
+
const stepLog = selectedResult.log.filter((l) => l.body.stepId === step.id);
|
|
1474
|
+
return {
|
|
1475
|
+
id: step.id,
|
|
1476
|
+
name: step.name,
|
|
1477
|
+
logString: stepLog.map((l) => l.body.message).join("\n"),
|
|
1478
|
+
status: (_b2 = (_a3 = stepLog[stepLog.length - 1]) == null ? void 0 : _a3.body.status) != null ? _b2 : "completed"
|
|
1479
|
+
};
|
|
1480
|
+
})) != null ? _a2 : [];
|
|
1481
|
+
}, [selectedResult]);
|
|
1482
|
+
if (!selectedResult) {
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
const selectedStep = (_a = steps.find((s) => s.id === currentStepId)) != null ? _a : steps[0];
|
|
1486
|
+
return /* @__PURE__ */ React.createElement(DryRunResultsSplitView, null, /* @__PURE__ */ React.createElement(TaskStatusStepper, {
|
|
1487
|
+
steps,
|
|
1488
|
+
currentStepId: selectedStep.id,
|
|
1489
|
+
onUserStepChange: setUserSelectedStepId
|
|
1490
|
+
}), /* @__PURE__ */ React.createElement(LogViewer, {
|
|
1491
|
+
text: (_b = selectedStep == null ? void 0 : selectedStep.logString) != null ? _b : ""
|
|
1492
|
+
}));
|
|
1493
|
+
}
|
|
1494
|
+
function OutputContent() {
|
|
1495
|
+
var _a, _b;
|
|
1496
|
+
const classes = useStyles$7();
|
|
1497
|
+
const { selectedResult } = useDryRun();
|
|
1498
|
+
if (!selectedResult) {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
return /* @__PURE__ */ React.createElement(DryRunResultsSplitView, null, /* @__PURE__ */ React.createElement(Box$1, {
|
|
1502
|
+
pt: 2
|
|
1503
|
+
}, ((_b = (_a = selectedResult.output) == null ? void 0 : _a.links) == null ? void 0 : _b.length) && /* @__PURE__ */ React.createElement(TaskPageLinks, {
|
|
1504
|
+
output: selectedResult.output
|
|
1505
|
+
})), /* @__PURE__ */ React.createElement(CodeMirror, {
|
|
1506
|
+
className: classes.codeMirror,
|
|
1507
|
+
theme: "dark",
|
|
1508
|
+
height: "100%",
|
|
1509
|
+
extensions: [StreamLanguage.define(yaml$1)],
|
|
1510
|
+
readOnly: true,
|
|
1511
|
+
value: JSON.stringify(selectedResult.output, null, 2)
|
|
1512
|
+
}));
|
|
1513
|
+
}
|
|
1514
|
+
function DryRunResultsView() {
|
|
1515
|
+
const classes = useStyles$7();
|
|
1516
|
+
const [selectedTab, setSelectedTab] = useState("files");
|
|
1517
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1518
|
+
className: classes.root
|
|
1519
|
+
}, /* @__PURE__ */ React.createElement(Tabs, {
|
|
1520
|
+
value: selectedTab,
|
|
1521
|
+
onChange: (_, v) => setSelectedTab(v)
|
|
1522
|
+
}, /* @__PURE__ */ React.createElement(Tab, {
|
|
1523
|
+
value: "files",
|
|
1524
|
+
label: "Files"
|
|
1525
|
+
}), /* @__PURE__ */ React.createElement(Tab, {
|
|
1526
|
+
value: "log",
|
|
1527
|
+
label: "Log"
|
|
1528
|
+
}), /* @__PURE__ */ React.createElement(Tab, {
|
|
1529
|
+
value: "output",
|
|
1530
|
+
label: "Output"
|
|
1531
|
+
})), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement("div", {
|
|
1532
|
+
className: classes.contentWrapper
|
|
1533
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
1534
|
+
className: classes.content
|
|
1535
|
+
}, selectedTab === "files" && /* @__PURE__ */ React.createElement(FilesContent, null), selectedTab === "log" && /* @__PURE__ */ React.createElement(LogContent, null), selectedTab === "output" && /* @__PURE__ */ React.createElement(OutputContent, null))));
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
const useStyles$6 = makeStyles$1((theme) => ({
|
|
1539
|
+
header: {
|
|
1540
|
+
height: 48,
|
|
1541
|
+
minHeight: 0,
|
|
1542
|
+
"&.Mui-expanded": {
|
|
1543
|
+
height: 48,
|
|
1544
|
+
minHeight: 0
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
content: {
|
|
1548
|
+
display: "grid",
|
|
1549
|
+
background: theme.palette.background.default,
|
|
1550
|
+
gridTemplateColumns: "180px auto 1fr",
|
|
1551
|
+
gridTemplateRows: "1fr",
|
|
1552
|
+
padding: 0,
|
|
1553
|
+
height: 400
|
|
1554
|
+
}
|
|
1555
|
+
}));
|
|
1556
|
+
function DryRunResults() {
|
|
1557
|
+
const classes = useStyles$6();
|
|
1558
|
+
const dryRun = useDryRun();
|
|
1559
|
+
const [expanded, setExpanded] = useState(false);
|
|
1560
|
+
const [hidden, setHidden] = useState(true);
|
|
1561
|
+
const resultsLength = dryRun.results.length;
|
|
1562
|
+
const prevResultsLength = usePrevious(resultsLength);
|
|
1563
|
+
useEffect(() => {
|
|
1564
|
+
if (prevResultsLength === 0 && resultsLength === 1) {
|
|
1565
|
+
setHidden(false);
|
|
1566
|
+
setExpanded(true);
|
|
1567
|
+
} else if (prevResultsLength === 1 && resultsLength === 0) {
|
|
1568
|
+
setExpanded(false);
|
|
1569
|
+
}
|
|
1570
|
+
}, [prevResultsLength, resultsLength]);
|
|
1571
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Accordion, {
|
|
1572
|
+
variant: "outlined",
|
|
1573
|
+
expanded,
|
|
1574
|
+
hidden: resultsLength === 0 && hidden,
|
|
1575
|
+
onChange: (_, exp) => setExpanded(exp),
|
|
1576
|
+
onTransitionEnd: () => resultsLength === 0 && setHidden(true)
|
|
1577
|
+
}, /* @__PURE__ */ React.createElement(AccordionSummary, {
|
|
1578
|
+
className: classes.header,
|
|
1579
|
+
expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon$1, null)
|
|
1580
|
+
}, /* @__PURE__ */ React.createElement(Typography$1, null, "Dry-run results")), /* @__PURE__ */ React.createElement(Divider, {
|
|
1581
|
+
orientation: "horizontal"
|
|
1582
|
+
}), /* @__PURE__ */ React.createElement(AccordionDetails, {
|
|
1583
|
+
className: classes.content
|
|
1584
|
+
}, /* @__PURE__ */ React.createElement(DryRunResultsList, null), /* @__PURE__ */ React.createElement(Divider, {
|
|
1585
|
+
orientation: "horizontal"
|
|
1586
|
+
}), /* @__PURE__ */ React.createElement(DryRunResultsView, null))));
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
const useStyles$5 = makeStyles((theme) => ({
|
|
1590
|
+
button: {
|
|
1591
|
+
padding: theme.spacing(1)
|
|
1592
|
+
},
|
|
1593
|
+
buttons: {
|
|
1594
|
+
display: "flex",
|
|
1595
|
+
flexFlow: "row nowrap",
|
|
1596
|
+
alignItems: "center",
|
|
1597
|
+
justifyContent: "flex-start"
|
|
1598
|
+
},
|
|
1599
|
+
buttonsGap: {
|
|
1600
|
+
flex: "1 1 auto"
|
|
1601
|
+
},
|
|
1602
|
+
buttonsDivider: {
|
|
1603
|
+
marginBottom: theme.spacing(1)
|
|
1604
|
+
}
|
|
1605
|
+
}));
|
|
1606
|
+
function TemplateEditorBrowser(props) {
|
|
1607
|
+
var _a, _b;
|
|
1608
|
+
const classes = useStyles$5();
|
|
1609
|
+
const directoryEditor = useDirectoryEditor();
|
|
1610
|
+
const changedFiles = directoryEditor.files.filter((file) => file.dirty);
|
|
1611
|
+
const handleClose = () => {
|
|
1612
|
+
if (!props.onClose) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
if (changedFiles.length > 0) {
|
|
1616
|
+
const accepted = window.confirm("Are you sure? Unsaved changes will be lost");
|
|
1617
|
+
if (!accepted) {
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
props.onClose();
|
|
1622
|
+
};
|
|
1623
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
|
|
1624
|
+
className: classes.buttons
|
|
1625
|
+
}, /* @__PURE__ */ React.createElement(Tooltip, {
|
|
1626
|
+
title: "Save all files"
|
|
1627
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
1628
|
+
className: classes.button,
|
|
1629
|
+
disabled: directoryEditor.files.every((file) => !file.dirty),
|
|
1630
|
+
onClick: () => directoryEditor.save()
|
|
1631
|
+
}, /* @__PURE__ */ React.createElement(SaveIcon, null))), /* @__PURE__ */ React.createElement(Tooltip, {
|
|
1632
|
+
title: "Reload directory"
|
|
1633
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
1634
|
+
className: classes.button,
|
|
1635
|
+
onClick: () => directoryEditor.reload()
|
|
1636
|
+
}, /* @__PURE__ */ React.createElement(RefreshIcon, null))), /* @__PURE__ */ React.createElement("div", {
|
|
1637
|
+
className: classes.buttonsGap
|
|
1638
|
+
}), /* @__PURE__ */ React.createElement(Tooltip, {
|
|
1639
|
+
title: "Close directory"
|
|
1640
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
1641
|
+
className: classes.button,
|
|
1642
|
+
onClick: handleClose
|
|
1643
|
+
}, /* @__PURE__ */ React.createElement(CloseIcon, null)))), /* @__PURE__ */ React.createElement(Divider$1, {
|
|
1644
|
+
className: classes.buttonsDivider
|
|
1645
|
+
}), /* @__PURE__ */ React.createElement(FileBrowser, {
|
|
1646
|
+
selected: (_b = (_a = directoryEditor.selectedFile) == null ? void 0 : _a.path) != null ? _b : "",
|
|
1647
|
+
onSelect: directoryEditor.setSelectedFile,
|
|
1648
|
+
filePaths: directoryEditor.files.map((file) => file.path)
|
|
1649
|
+
}));
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const useStyles$4 = makeStyles$1({
|
|
1653
|
+
containerWrapper: {
|
|
1654
|
+
position: "relative",
|
|
1655
|
+
width: "100%",
|
|
1656
|
+
height: "100%"
|
|
1657
|
+
},
|
|
1658
|
+
container: {
|
|
1659
|
+
position: "absolute",
|
|
1660
|
+
top: 0,
|
|
1661
|
+
bottom: 0,
|
|
1662
|
+
left: 0,
|
|
1663
|
+
right: 0,
|
|
1664
|
+
overflow: "auto"
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
class ErrorBoundary extends Component {
|
|
1668
|
+
constructor() {
|
|
1669
|
+
super(...arguments);
|
|
1670
|
+
this.state = {
|
|
1671
|
+
shouldRender: true
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
componentDidUpdate(prevProps) {
|
|
1675
|
+
if (prevProps.invalidator !== this.props.invalidator) {
|
|
1676
|
+
this.setState({ shouldRender: true });
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
componentDidCatch(error) {
|
|
1680
|
+
this.props.setErrorText(error.message);
|
|
1681
|
+
this.setState({ shouldRender: false });
|
|
1682
|
+
}
|
|
1683
|
+
render() {
|
|
1684
|
+
return this.state.shouldRender ? this.props.children : null;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
function isJsonObject(value) {
|
|
1688
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1689
|
+
}
|
|
1690
|
+
function TemplateEditorForm(props) {
|
|
1691
|
+
const {
|
|
1692
|
+
content,
|
|
1693
|
+
contentIsSpec,
|
|
1694
|
+
data,
|
|
1695
|
+
onUpdate,
|
|
1696
|
+
onDryRun,
|
|
1697
|
+
setErrorText,
|
|
1698
|
+
fieldExtensions = []
|
|
1699
|
+
} = props;
|
|
1700
|
+
const classes = useStyles$4();
|
|
1701
|
+
const apiHolder = useApiHolder();
|
|
1702
|
+
const [steps, setSteps] = useState();
|
|
1703
|
+
const fields = useMemo(() => {
|
|
1704
|
+
return Object.fromEntries(fieldExtensions.map(({ name, component }) => [name, component]));
|
|
1705
|
+
}, [fieldExtensions]);
|
|
1706
|
+
useDebounce(() => {
|
|
1707
|
+
try {
|
|
1708
|
+
if (!content) {
|
|
1709
|
+
setSteps(void 0);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const parsed = yaml.parse(content);
|
|
1713
|
+
if (!isJsonObject(parsed)) {
|
|
1714
|
+
setSteps(void 0);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
let rootObj = parsed;
|
|
1718
|
+
if (!contentIsSpec) {
|
|
1719
|
+
const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
|
|
1720
|
+
if (!isTemplate) {
|
|
1721
|
+
setSteps(void 0);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
|
|
1725
|
+
}
|
|
1726
|
+
const { parameters } = rootObj;
|
|
1727
|
+
if (!Array.isArray(parameters)) {
|
|
1728
|
+
setErrorText("Template parameters must be an array");
|
|
1729
|
+
setSteps(void 0);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const fieldValidators = Object.fromEntries(fieldExtensions.map(({ name, validation }) => [name, validation]));
|
|
1733
|
+
setErrorText();
|
|
1734
|
+
setSteps(parameters.flatMap((param) => isJsonObject(param) ? [
|
|
1735
|
+
{
|
|
1736
|
+
title: String(param.title),
|
|
1737
|
+
schema: param,
|
|
1738
|
+
validate: createValidator(param, fieldValidators, {
|
|
1739
|
+
apiHolder
|
|
1740
|
+
})
|
|
1741
|
+
}
|
|
1742
|
+
] : []));
|
|
1743
|
+
} catch (e) {
|
|
1744
|
+
setErrorText(e.message);
|
|
1745
|
+
}
|
|
1746
|
+
}, 250, [contentIsSpec, content, apiHolder]);
|
|
1747
|
+
if (!steps) {
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1751
|
+
className: classes.containerWrapper
|
|
1752
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
1753
|
+
className: classes.container
|
|
1754
|
+
}, /* @__PURE__ */ React.createElement(ErrorBoundary, {
|
|
1755
|
+
invalidator: steps,
|
|
1756
|
+
setErrorText
|
|
1757
|
+
}, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
|
|
1758
|
+
steps,
|
|
1759
|
+
fields,
|
|
1760
|
+
formData: data,
|
|
1761
|
+
onChange: (e) => onUpdate(e.formData),
|
|
1762
|
+
onReset: () => onUpdate({}),
|
|
1763
|
+
finishButtonLabel: onDryRun && "Try It",
|
|
1764
|
+
onFinish: onDryRun && (() => onDryRun(data))
|
|
1765
|
+
}))));
|
|
1766
|
+
}
|
|
1767
|
+
function TemplateEditorFormDirectoryEditorDryRun(props) {
|
|
1768
|
+
const { setErrorText, fieldExtensions = [] } = props;
|
|
1769
|
+
const dryRun = useDryRun();
|
|
1770
|
+
const directoryEditor = useDirectoryEditor();
|
|
1771
|
+
const { selectedFile } = directoryEditor;
|
|
1772
|
+
const [data, setData] = useState({});
|
|
1773
|
+
const handleDryRun = async () => {
|
|
1774
|
+
if (!selectedFile) {
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
await dryRun.execute({
|
|
1778
|
+
templateContent: selectedFile.content,
|
|
1779
|
+
values: data,
|
|
1780
|
+
files: directoryEditor.files
|
|
1781
|
+
});
|
|
1782
|
+
};
|
|
1783
|
+
const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
|
|
1784
|
+
return /* @__PURE__ */ React.createElement(TemplateEditorForm, {
|
|
1785
|
+
onDryRun: handleDryRun,
|
|
1786
|
+
fieldExtensions,
|
|
1787
|
+
setErrorText,
|
|
1788
|
+
content,
|
|
1789
|
+
data,
|
|
1790
|
+
onUpdate: setData
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
|
|
1794
|
+
|
|
1795
|
+
const useStyles$3 = makeStyles((theme) => ({
|
|
1796
|
+
container: {
|
|
1797
|
+
position: "relative",
|
|
1798
|
+
width: "100%",
|
|
1799
|
+
height: "100%"
|
|
1800
|
+
},
|
|
1801
|
+
codeMirror: {
|
|
1802
|
+
position: "absolute",
|
|
1803
|
+
top: 0,
|
|
1804
|
+
bottom: 0,
|
|
1805
|
+
left: 0,
|
|
1806
|
+
right: 0
|
|
1807
|
+
},
|
|
1808
|
+
errorPanel: {
|
|
1809
|
+
color: theme.palette.error.main,
|
|
1810
|
+
lineHeight: 2,
|
|
1811
|
+
margin: theme.spacing(0, 1)
|
|
1812
|
+
},
|
|
1813
|
+
floatingButtons: {
|
|
1814
|
+
position: "absolute",
|
|
1815
|
+
top: theme.spacing(1),
|
|
1816
|
+
right: theme.spacing(3)
|
|
1817
|
+
},
|
|
1818
|
+
floatingButton: {
|
|
1819
|
+
padding: theme.spacing(1)
|
|
1820
|
+
}
|
|
1821
|
+
}));
|
|
1822
|
+
function TemplateEditorTextArea(props) {
|
|
1823
|
+
const { errorText } = props;
|
|
1824
|
+
const classes = useStyles$3();
|
|
1825
|
+
const panelExtension = useMemo(() => {
|
|
1826
|
+
if (!errorText) {
|
|
1827
|
+
return showPanel.of(null);
|
|
1828
|
+
}
|
|
1829
|
+
const dom = document.createElement("div");
|
|
1830
|
+
dom.classList.add(classes.errorPanel);
|
|
1831
|
+
dom.textContent = errorText;
|
|
1832
|
+
return showPanel.of(() => ({ dom, bottom: true }));
|
|
1833
|
+
}, [classes, errorText]);
|
|
1834
|
+
useKeyboardEvent((e) => e.key === "s" && (e.ctrlKey || e.metaKey), (e) => {
|
|
1835
|
+
e.preventDefault();
|
|
1836
|
+
if (props.onSave) {
|
|
1837
|
+
props.onSave();
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1841
|
+
className: classes.container
|
|
1842
|
+
}, /* @__PURE__ */ React.createElement(CodeMirror, {
|
|
1843
|
+
className: classes.codeMirror,
|
|
1844
|
+
theme: "dark",
|
|
1845
|
+
height: "100%",
|
|
1846
|
+
extensions: [StreamLanguage.define(yaml$1), panelExtension],
|
|
1847
|
+
value: props.content,
|
|
1848
|
+
onChange: props.onUpdate
|
|
1849
|
+
}), (props.onSave || props.onReload) && /* @__PURE__ */ React.createElement("div", {
|
|
1850
|
+
className: classes.floatingButtons
|
|
1851
|
+
}, /* @__PURE__ */ React.createElement(Paper, null, props.onSave && /* @__PURE__ */ React.createElement(Tooltip, {
|
|
1852
|
+
title: "Save file"
|
|
1853
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
1854
|
+
className: classes.floatingButton,
|
|
1855
|
+
onClick: () => {
|
|
1856
|
+
var _a;
|
|
1857
|
+
return (_a = props.onSave) == null ? void 0 : _a.call(props);
|
|
1858
|
+
}
|
|
1859
|
+
}, /* @__PURE__ */ React.createElement(SaveIcon, null))), props.onReload && /* @__PURE__ */ React.createElement(Tooltip, {
|
|
1860
|
+
title: "Reload file"
|
|
1861
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
1862
|
+
className: classes.floatingButton,
|
|
1863
|
+
onClick: () => {
|
|
1864
|
+
var _a;
|
|
1865
|
+
return (_a = props.onReload) == null ? void 0 : _a.call(props);
|
|
1866
|
+
}
|
|
1867
|
+
}, /* @__PURE__ */ React.createElement(RefreshIcon, null))))));
|
|
1868
|
+
}
|
|
1869
|
+
function TemplateEditorDirectoryEditorTextArea(props) {
|
|
1870
|
+
var _a, _b;
|
|
1871
|
+
const directoryEditor = useDirectoryEditor();
|
|
1872
|
+
const actions = ((_a = directoryEditor.selectedFile) == null ? void 0 : _a.dirty) ? {
|
|
1873
|
+
onSave: () => directoryEditor.save(),
|
|
1874
|
+
onReload: () => directoryEditor.reload()
|
|
1875
|
+
} : {
|
|
1876
|
+
onReload: () => directoryEditor.reload()
|
|
1877
|
+
};
|
|
1878
|
+
return /* @__PURE__ */ React.createElement(TemplateEditorTextArea, {
|
|
1879
|
+
errorText: props.errorText,
|
|
1880
|
+
content: (_b = directoryEditor.selectedFile) == null ? void 0 : _b.content,
|
|
1881
|
+
onUpdate: (content) => {
|
|
1882
|
+
var _a2;
|
|
1883
|
+
return (_a2 = directoryEditor.selectedFile) == null ? void 0 : _a2.updateContent(content);
|
|
1884
|
+
},
|
|
1885
|
+
...actions
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
TemplateEditorTextArea.DirectoryEditor = TemplateEditorDirectoryEditorTextArea;
|
|
1889
|
+
|
|
1890
|
+
const useStyles$2 = makeStyles({
|
|
1891
|
+
root: {
|
|
1892
|
+
gridArea: "pageContent",
|
|
1893
|
+
display: "grid",
|
|
1894
|
+
gridTemplateAreas: `
|
|
1895
|
+
"browser editor preview"
|
|
1896
|
+
"results results results"
|
|
1897
|
+
`,
|
|
1898
|
+
gridTemplateColumns: "1fr 3fr 2fr",
|
|
1899
|
+
gridTemplateRows: "1fr auto"
|
|
1900
|
+
},
|
|
1901
|
+
browser: {
|
|
1902
|
+
gridArea: "browser",
|
|
1903
|
+
overflow: "auto"
|
|
1904
|
+
},
|
|
1905
|
+
editor: {
|
|
1906
|
+
gridArea: "editor",
|
|
1907
|
+
overflow: "auto"
|
|
1908
|
+
},
|
|
1909
|
+
preview: {
|
|
1910
|
+
gridArea: "preview",
|
|
1911
|
+
overflow: "auto"
|
|
1912
|
+
},
|
|
1913
|
+
results: {
|
|
1914
|
+
gridArea: "results"
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
const TemplateEditor = (props) => {
|
|
1918
|
+
const classes = useStyles$2();
|
|
1919
|
+
const [errorText, setErrorText] = useState();
|
|
1920
|
+
return /* @__PURE__ */ React.createElement(DirectoryEditorProvider, {
|
|
1921
|
+
directory: props.directory
|
|
1922
|
+
}, /* @__PURE__ */ React.createElement(DryRunProvider, null, /* @__PURE__ */ React.createElement("main", {
|
|
1923
|
+
className: classes.root
|
|
1924
|
+
}, /* @__PURE__ */ React.createElement("section", {
|
|
1925
|
+
className: classes.browser
|
|
1926
|
+
}, /* @__PURE__ */ React.createElement(TemplateEditorBrowser, {
|
|
1927
|
+
onClose: props.onClose
|
|
1928
|
+
})), /* @__PURE__ */ React.createElement("section", {
|
|
1929
|
+
className: classes.editor
|
|
1930
|
+
}, /* @__PURE__ */ React.createElement(TemplateEditorTextArea.DirectoryEditor, {
|
|
1931
|
+
errorText
|
|
1932
|
+
})), /* @__PURE__ */ React.createElement("section", {
|
|
1933
|
+
className: classes.preview
|
|
1934
|
+
}, /* @__PURE__ */ React.createElement(TemplateEditorForm.DirectoryEditorDryRun, {
|
|
1935
|
+
setErrorText,
|
|
1936
|
+
fieldExtensions: props.fieldExtensions
|
|
1937
|
+
})), /* @__PURE__ */ React.createElement("section", {
|
|
1938
|
+
className: classes.results
|
|
1939
|
+
}, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1942
|
+
const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
|
|
1943
|
+
parameters:
|
|
1944
|
+
- title: Fill in some steps
|
|
1945
|
+
required:
|
|
1946
|
+
- name
|
|
1947
|
+
properties:
|
|
1948
|
+
name:
|
|
1949
|
+
title: Name
|
|
1950
|
+
type: string
|
|
1951
|
+
description: Unique name of the component
|
|
1952
|
+
owner:
|
|
1953
|
+
title: Owner
|
|
1954
|
+
type: string
|
|
1955
|
+
description: Owner of the component
|
|
1956
|
+
ui:field: OwnerPicker
|
|
1957
|
+
ui:options:
|
|
1958
|
+
allowedKinds:
|
|
1959
|
+
- Group
|
|
1960
|
+
- title: Choose a location
|
|
1961
|
+
required:
|
|
1962
|
+
- repoUrl
|
|
1963
|
+
properties:
|
|
1964
|
+
repoUrl:
|
|
1965
|
+
title: Repository Location
|
|
1966
|
+
type: string
|
|
1967
|
+
ui:field: RepoUrlPicker
|
|
1968
|
+
ui:options:
|
|
1969
|
+
allowedHosts:
|
|
1970
|
+
- github.com
|
|
1971
|
+
steps:
|
|
1972
|
+
- id: fetch-base
|
|
1973
|
+
name: Fetch Base
|
|
1974
|
+
action: fetch:template
|
|
1975
|
+
input:
|
|
1976
|
+
url: ./template
|
|
1977
|
+
values:
|
|
1978
|
+
name: \${{parameters.name}}
|
|
1979
|
+
`;
|
|
1980
|
+
const useStyles$1 = makeStyles((theme) => ({
|
|
1981
|
+
root: {
|
|
1982
|
+
gridArea: "pageContent",
|
|
1983
|
+
display: "grid",
|
|
1984
|
+
gridTemplateAreas: `
|
|
1985
|
+
"controls controls"
|
|
1986
|
+
"textArea preview"
|
|
1987
|
+
`,
|
|
1988
|
+
gridTemplateRows: "auto 1fr",
|
|
1989
|
+
gridTemplateColumns: "1fr 1fr"
|
|
1990
|
+
},
|
|
1991
|
+
controls: {
|
|
1992
|
+
gridArea: "controls",
|
|
1993
|
+
display: "flex",
|
|
1994
|
+
flexFlow: "row nowrap",
|
|
1995
|
+
alignItems: "center",
|
|
1996
|
+
margin: theme.spacing(1)
|
|
1997
|
+
},
|
|
1998
|
+
textArea: {
|
|
1999
|
+
gridArea: "textArea"
|
|
2000
|
+
},
|
|
2001
|
+
preview: {
|
|
2002
|
+
gridArea: "preview"
|
|
2003
|
+
}
|
|
2004
|
+
}));
|
|
2005
|
+
const TemplateFormPreviewer = ({
|
|
2006
|
+
defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
|
|
2007
|
+
customFieldExtensions = [],
|
|
2008
|
+
onClose
|
|
2009
|
+
}) => {
|
|
2010
|
+
const classes = useStyles$1();
|
|
2011
|
+
const alertApi = useApi(alertApiRef);
|
|
2012
|
+
const catalogApi = useApi(catalogApiRef);
|
|
2013
|
+
const [selectedTemplate, setSelectedTemplate] = useState("");
|
|
2014
|
+
const [errorText, setErrorText] = useState();
|
|
2015
|
+
const [templateOptions, setTemplateOptions] = useState([]);
|
|
2016
|
+
const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
|
|
2017
|
+
const [formState, setFormState] = useState({});
|
|
2018
|
+
const { loading } = useAsync(() => catalogApi.getEntities({
|
|
2019
|
+
filter: { kind: "template" },
|
|
2020
|
+
fields: [
|
|
2021
|
+
"kind",
|
|
2022
|
+
"metadata.namespace",
|
|
2023
|
+
"metadata.name",
|
|
2024
|
+
"metadata.title",
|
|
2025
|
+
"spec.parameters",
|
|
2026
|
+
"spec.steps",
|
|
2027
|
+
"spec.output"
|
|
2028
|
+
]
|
|
2029
|
+
}).then(({ items }) => setTemplateOptions(items.map((template) => {
|
|
2030
|
+
var _a;
|
|
2031
|
+
return {
|
|
2032
|
+
label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
|
|
2033
|
+
value: template
|
|
2034
|
+
};
|
|
2035
|
+
}))).catch((e) => alertApi.post({
|
|
2036
|
+
message: `Error loading exisiting templates: ${e.message}`,
|
|
2037
|
+
severity: "error"
|
|
2038
|
+
})), [catalogApi]);
|
|
2039
|
+
const handleSelectChange = useCallback((selected) => {
|
|
2040
|
+
setSelectedTemplate(selected);
|
|
2041
|
+
setTemplateYaml(yaml.stringify(selected.spec));
|
|
2042
|
+
}, [setTemplateYaml]);
|
|
2043
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement("main", {
|
|
2044
|
+
className: classes.root
|
|
2045
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
2046
|
+
className: classes.controls
|
|
2047
|
+
}, /* @__PURE__ */ React.createElement(FormControl, {
|
|
2048
|
+
variant: "outlined",
|
|
2049
|
+
size: "small",
|
|
2050
|
+
fullWidth: true
|
|
2051
|
+
}, /* @__PURE__ */ React.createElement(InputLabel, {
|
|
2052
|
+
id: "select-template-label"
|
|
2053
|
+
}, "Load Existing Template"), /* @__PURE__ */ React.createElement(Select, {
|
|
2054
|
+
value: selectedTemplate,
|
|
2055
|
+
label: "Load Existing Template",
|
|
2056
|
+
labelId: "select-template-label",
|
|
2057
|
+
onChange: (e) => handleSelectChange(e.target.value)
|
|
2058
|
+
}, templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, {
|
|
2059
|
+
key: idx,
|
|
2060
|
+
value: option.value
|
|
2061
|
+
}, option.label)))), /* @__PURE__ */ React.createElement(IconButton, {
|
|
2062
|
+
size: "medium",
|
|
2063
|
+
onClick: onClose
|
|
2064
|
+
}, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", {
|
|
2065
|
+
className: classes.textArea
|
|
2066
|
+
}, /* @__PURE__ */ React.createElement(TemplateEditorTextArea, {
|
|
2067
|
+
content: templateYaml,
|
|
2068
|
+
onUpdate: setTemplateYaml,
|
|
2069
|
+
errorText
|
|
2070
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
2071
|
+
className: classes.preview
|
|
2072
|
+
}, /* @__PURE__ */ React.createElement(TemplateEditorForm, {
|
|
2073
|
+
content: templateYaml,
|
|
2074
|
+
contentIsSpec: true,
|
|
2075
|
+
fieldExtensions: customFieldExtensions,
|
|
2076
|
+
data: formState,
|
|
2077
|
+
onUpdate: setFormState,
|
|
2078
|
+
setErrorText
|
|
2079
|
+
}))));
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
function TemplateEditorPage(props) {
|
|
2083
|
+
const [selection, setSelection] = useState();
|
|
2084
|
+
let content = null;
|
|
2085
|
+
if ((selection == null ? void 0 : selection.type) === "local") {
|
|
2086
|
+
content = /* @__PURE__ */ React.createElement(TemplateEditor, {
|
|
2087
|
+
directory: selection.directory,
|
|
2088
|
+
fieldExtensions: props.customFieldExtensions,
|
|
2089
|
+
onClose: () => setSelection(void 0)
|
|
2090
|
+
});
|
|
2091
|
+
} else if ((selection == null ? void 0 : selection.type) === "form") {
|
|
2092
|
+
content = /* @__PURE__ */ React.createElement(TemplateFormPreviewer, {
|
|
2093
|
+
defaultPreviewTemplate: props.defaultPreviewTemplate,
|
|
2094
|
+
customFieldExtensions: props.customFieldExtensions,
|
|
2095
|
+
onClose: () => setSelection(void 0)
|
|
2096
|
+
});
|
|
2097
|
+
} else {
|
|
2098
|
+
content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(TemplateEditorIntro, {
|
|
2099
|
+
onSelect: (option) => {
|
|
2100
|
+
if (option === "local") {
|
|
2101
|
+
WebFileSystemAccess.requestDirectoryAccess().then((directory) => setSelection({ type: "local", directory })).catch(() => {
|
|
2102
|
+
});
|
|
2103
|
+
} else if (option === "form") {
|
|
2104
|
+
setSelection({ type: "form" });
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}));
|
|
2108
|
+
}
|
|
2109
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
2110
|
+
themeId: "home"
|
|
2111
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
2112
|
+
title: "Template Editor",
|
|
2113
|
+
subtitle: "Edit, preview, and try out templates and template forms"
|
|
2114
|
+
}), content);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
const useStyles = makeStyles((theme) => ({
|
|
2118
|
+
root: {
|
|
2119
|
+
backgroundColor: "rgba(0, 0, 0, .11)",
|
|
2120
|
+
boxShadow: "none",
|
|
2121
|
+
margin: theme.spacing(1, 0, 1, 0)
|
|
2122
|
+
},
|
|
2123
|
+
title: {
|
|
2124
|
+
margin: theme.spacing(1, 0, 0, 1),
|
|
2125
|
+
textTransform: "uppercase",
|
|
2126
|
+
fontSize: 12,
|
|
2127
|
+
fontWeight: "bold"
|
|
2128
|
+
},
|
|
2129
|
+
listIcon: {
|
|
2130
|
+
minWidth: 30,
|
|
2131
|
+
color: theme.palette.text.primary
|
|
2132
|
+
},
|
|
2133
|
+
menuItem: {
|
|
2134
|
+
minHeight: theme.spacing(6)
|
|
2135
|
+
},
|
|
2136
|
+
groupWrapper: {
|
|
2137
|
+
margin: theme.spacing(1, 1, 2, 1)
|
|
2138
|
+
}
|
|
2139
|
+
}), {
|
|
2140
|
+
name: "ScaffolderReactOwnerListPicker"
|
|
2141
|
+
});
|
|
2142
|
+
function getFilterGroups() {
|
|
2143
|
+
return [
|
|
2144
|
+
{
|
|
2145
|
+
name: "Task Owner",
|
|
2146
|
+
items: [
|
|
2147
|
+
{
|
|
2148
|
+
id: "owned",
|
|
2149
|
+
label: "Owned",
|
|
2150
|
+
icon: SettingsIcon
|
|
2151
|
+
},
|
|
2152
|
+
{
|
|
2153
|
+
id: "all",
|
|
2154
|
+
label: "All",
|
|
2155
|
+
icon: AllIcon
|
|
2156
|
+
}
|
|
2157
|
+
]
|
|
2158
|
+
}
|
|
2159
|
+
];
|
|
2160
|
+
}
|
|
2161
|
+
const OwnerListPicker = (props) => {
|
|
2162
|
+
const { filter, onSelectOwner } = props;
|
|
2163
|
+
const classes = useStyles();
|
|
2164
|
+
const filterGroups = getFilterGroups();
|
|
2165
|
+
return /* @__PURE__ */ React.createElement(Card, {
|
|
2166
|
+
className: classes.root
|
|
2167
|
+
}, filterGroups.map((group) => /* @__PURE__ */ React.createElement(Fragment, {
|
|
2168
|
+
key: group.name
|
|
2169
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
2170
|
+
variant: "subtitle2",
|
|
2171
|
+
className: classes.title
|
|
2172
|
+
}, group.name), /* @__PURE__ */ React.createElement(Card, {
|
|
2173
|
+
className: classes.groupWrapper
|
|
2174
|
+
}, /* @__PURE__ */ React.createElement(List$2, {
|
|
2175
|
+
disablePadding: true,
|
|
2176
|
+
dense: true
|
|
2177
|
+
}, group.items.map((item) => /* @__PURE__ */ React.createElement(MenuItem$1, {
|
|
2178
|
+
key: item.id,
|
|
2179
|
+
button: true,
|
|
2180
|
+
divider: true,
|
|
2181
|
+
onClick: () => onSelectOwner(item.id),
|
|
2182
|
+
selected: item.id === filter,
|
|
2183
|
+
className: classes.menuItem,
|
|
2184
|
+
"data-testid": `owner-picker-${item.id}`
|
|
2185
|
+
}, item.icon && /* @__PURE__ */ React.createElement(ListItemIcon$1, {
|
|
2186
|
+
className: classes.listIcon
|
|
2187
|
+
}, /* @__PURE__ */ React.createElement(item.icon, {
|
|
2188
|
+
fontSize: "small"
|
|
2189
|
+
})), /* @__PURE__ */ React.createElement(ListItemText$1, null, /* @__PURE__ */ React.createElement(Typography, {
|
|
2190
|
+
variant: "body1"
|
|
2191
|
+
}, item.label)))))))));
|
|
2192
|
+
};
|
|
2193
|
+
|
|
2194
|
+
const CreatedAtColumn = ({ createdAt }) => {
|
|
2195
|
+
const createdAtTime = DateTime.fromISO(createdAt);
|
|
2196
|
+
const formatted = Interval.fromDateTimes(createdAtTime, DateTime.local()).toDuration().valueOf();
|
|
2197
|
+
return /* @__PURE__ */ React.createElement("p", null, humanizeDuration(formatted, { round: true }), " ago");
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
const OwnerEntityColumn = ({ entityRef }) => {
|
|
2201
|
+
var _a, _b, _c;
|
|
2202
|
+
const catalogApi = useApi(catalogApiRef);
|
|
2203
|
+
const { value, loading, error } = useAsync(() => catalogApi.getEntityByRef(entityRef || ""), [catalogApi, entityRef]);
|
|
2204
|
+
if (!entityRef) {
|
|
2205
|
+
return /* @__PURE__ */ React.createElement("p", null, "Unknown");
|
|
2206
|
+
}
|
|
2207
|
+
if (loading || error) {
|
|
2208
|
+
return null;
|
|
2209
|
+
}
|
|
2210
|
+
return /* @__PURE__ */ React.createElement(EntityRefLink, {
|
|
2211
|
+
entityRef: parseEntityRef(entityRef),
|
|
2212
|
+
title: (_c = (_b = (_a = value == null ? void 0 : value.spec) == null ? void 0 : _a.profile) == null ? void 0 : _b.displayName) != null ? _c : value == null ? void 0 : value.metadata.name
|
|
2213
|
+
});
|
|
2214
|
+
};
|
|
2215
|
+
|
|
2216
|
+
const TaskStatusColumn = ({ status }) => {
|
|
2217
|
+
switch (status) {
|
|
2218
|
+
case "processing":
|
|
2219
|
+
return /* @__PURE__ */ React.createElement(StatusPending, null, status);
|
|
2220
|
+
case "completed":
|
|
2221
|
+
return /* @__PURE__ */ React.createElement(StatusOK, null, status);
|
|
2222
|
+
case "error":
|
|
2223
|
+
default:
|
|
2224
|
+
return /* @__PURE__ */ React.createElement(StatusError, null, status);
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
|
|
2228
|
+
const TemplateTitleColumn = ({ entityRef }) => {
|
|
2229
|
+
const scaffolder = useApi(scaffolderApiRef);
|
|
2230
|
+
const { value, loading, error } = useAsync(() => scaffolder.getTemplateParameterSchema(entityRef || ""), [scaffolder, entityRef]);
|
|
2231
|
+
if (loading || error || !entityRef) {
|
|
2232
|
+
return null;
|
|
2233
|
+
}
|
|
2234
|
+
return /* @__PURE__ */ React.createElement(EntityRefLink, {
|
|
2235
|
+
entityRef: parseEntityRef(entityRef),
|
|
2236
|
+
title: value == null ? void 0 : value.title
|
|
2237
|
+
});
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
const ListTaskPageContent = (props) => {
|
|
2241
|
+
var _a;
|
|
2242
|
+
const { initiallySelectedFilter = "owned" } = props;
|
|
2243
|
+
const scaffolderApi = useApi(scaffolderApiRef);
|
|
2244
|
+
const rootLink = useRouteRef(rootRouteRef);
|
|
2245
|
+
const [ownerFilter, setOwnerFilter] = useState(initiallySelectedFilter);
|
|
2246
|
+
const { value, loading, error } = useAsync(() => {
|
|
2247
|
+
var _a2;
|
|
2248
|
+
if (scaffolderApi.listTasks) {
|
|
2249
|
+
return (_a2 = scaffolderApi.listTasks) == null ? void 0 : _a2.call(scaffolderApi, { filterByOwnership: ownerFilter });
|
|
2250
|
+
}
|
|
2251
|
+
console.warn("listTasks is not implemented in the scaffolderApi, please make sure to implement this method.");
|
|
2252
|
+
return Promise.resolve({ tasks: [] });
|
|
2253
|
+
}, [scaffolderApi, ownerFilter]);
|
|
2254
|
+
if (loading) {
|
|
2255
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
2256
|
+
}
|
|
2257
|
+
if (error) {
|
|
2258
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ErrorPanel, {
|
|
2259
|
+
error
|
|
2260
|
+
}), /* @__PURE__ */ React.createElement(EmptyState, {
|
|
2261
|
+
missing: "info",
|
|
2262
|
+
title: "No information to display",
|
|
2263
|
+
description: "There is no Tasks or there was an issue communicating with backend."
|
|
2264
|
+
}));
|
|
2265
|
+
}
|
|
2266
|
+
return /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(OwnerListPicker, {
|
|
2267
|
+
filter: ownerFilter,
|
|
2268
|
+
onSelectOwner: (id) => setOwnerFilter(id)
|
|
2269
|
+
})), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, /* @__PURE__ */ React.createElement(MaterialTable, {
|
|
2270
|
+
data: (_a = value == null ? void 0 : value.tasks) != null ? _a : [],
|
|
2271
|
+
title: "Tasks",
|
|
2272
|
+
columns: [
|
|
2273
|
+
{
|
|
2274
|
+
title: "Task ID",
|
|
2275
|
+
field: "id",
|
|
2276
|
+
render: (row) => /* @__PURE__ */ React.createElement(Link$1, {
|
|
2277
|
+
to: `${rootLink()}/tasks/${row.id}`
|
|
2278
|
+
}, row.id)
|
|
2279
|
+
},
|
|
2280
|
+
{
|
|
2281
|
+
title: "Template",
|
|
2282
|
+
render: (row) => {
|
|
2283
|
+
var _a2;
|
|
2284
|
+
return /* @__PURE__ */ React.createElement(TemplateTitleColumn, {
|
|
2285
|
+
entityRef: (_a2 = row.spec.templateInfo) == null ? void 0 : _a2.entityRef
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
title: "Created",
|
|
2291
|
+
field: "createdAt",
|
|
2292
|
+
render: (row) => /* @__PURE__ */ React.createElement(CreatedAtColumn, {
|
|
2293
|
+
createdAt: row.createdAt
|
|
2294
|
+
})
|
|
2295
|
+
},
|
|
2296
|
+
{
|
|
2297
|
+
title: "Owner",
|
|
2298
|
+
field: "createdBy",
|
|
2299
|
+
render: (row) => {
|
|
2300
|
+
var _a2, _b;
|
|
2301
|
+
return /* @__PURE__ */ React.createElement(OwnerEntityColumn, {
|
|
2302
|
+
entityRef: (_b = (_a2 = row.spec) == null ? void 0 : _a2.user) == null ? void 0 : _b.ref
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
},
|
|
2306
|
+
{
|
|
2307
|
+
title: "Status",
|
|
2308
|
+
field: "status",
|
|
2309
|
+
render: (row) => /* @__PURE__ */ React.createElement(TaskStatusColumn, {
|
|
2310
|
+
status: row.status
|
|
2311
|
+
})
|
|
2312
|
+
}
|
|
2313
|
+
]
|
|
2314
|
+
})));
|
|
2315
|
+
};
|
|
2316
|
+
const ListTasksPage = (props) => {
|
|
2317
|
+
return /* @__PURE__ */ React.createElement(Page, {
|
|
2318
|
+
themeId: "home"
|
|
2319
|
+
}, /* @__PURE__ */ React.createElement(Header, {
|
|
2320
|
+
pageTitleOverride: "Templates Tasks",
|
|
2321
|
+
title: /* @__PURE__ */ React.createElement(React.Fragment, null, "List template tasks ", /* @__PURE__ */ React.createElement(Lifecycle, {
|
|
2322
|
+
shorthand: true,
|
|
2323
|
+
alpha: true
|
|
2324
|
+
})),
|
|
2325
|
+
subtitle: "All tasks that have been started"
|
|
2326
|
+
}), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ListTaskPageContent, {
|
|
2327
|
+
...props
|
|
2328
|
+
})));
|
|
2329
|
+
};
|
|
2330
|
+
|
|
2331
|
+
const Router = (props) => {
|
|
2332
|
+
const { groups, components = {}, defaultPreviewTemplate } = props;
|
|
2333
|
+
const { TemplateCardComponent, TaskPageComponent } = components;
|
|
2334
|
+
const outlet = useOutlet();
|
|
2335
|
+
const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
|
|
2336
|
+
const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
|
|
2337
|
+
key: FIELD_EXTENSION_WRAPPER_KEY
|
|
2338
|
+
}).findComponentData({
|
|
2339
|
+
key: FIELD_EXTENSION_KEY
|
|
2340
|
+
}));
|
|
2341
|
+
const fieldExtensions = [
|
|
2342
|
+
...customFieldExtensions,
|
|
2343
|
+
...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({ name }) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
|
|
2344
|
+
];
|
|
2345
|
+
return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, {
|
|
2346
|
+
element: /* @__PURE__ */ React.createElement(ScaffolderPage, {
|
|
2347
|
+
groups,
|
|
2348
|
+
TemplateCardComponent,
|
|
2349
|
+
contextMenu: props.contextMenu
|
|
2350
|
+
})
|
|
2351
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
2352
|
+
path: selectedTemplateRouteRef.path,
|
|
2353
|
+
element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePage, {
|
|
2354
|
+
customFieldExtensions: fieldExtensions
|
|
2355
|
+
}))
|
|
2356
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
2357
|
+
path: scaffolderListTaskRouteRef.path,
|
|
2358
|
+
element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
|
|
2359
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
2360
|
+
path: scaffolderTaskRouteRef.path,
|
|
2361
|
+
element: /* @__PURE__ */ React.createElement(TaskPageElement, null)
|
|
2362
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
2363
|
+
path: actionsRouteRef.path,
|
|
2364
|
+
element: /* @__PURE__ */ React.createElement(ActionsPage, null)
|
|
2365
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
2366
|
+
path: editRouteRef.path,
|
|
2367
|
+
element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplateEditorPage, {
|
|
2368
|
+
defaultPreviewTemplate,
|
|
2369
|
+
customFieldExtensions: fieldExtensions
|
|
2370
|
+
}))
|
|
2371
|
+
}), /* @__PURE__ */ React.createElement(Route, {
|
|
2372
|
+
path: "preview",
|
|
2373
|
+
element: /* @__PURE__ */ React.createElement(Navigate, {
|
|
2374
|
+
to: "../edit"
|
|
2375
|
+
})
|
|
2376
|
+
}));
|
|
2377
|
+
};
|
|
2378
|
+
|
|
2379
|
+
export { Router };
|
|
2380
|
+
//# sourceMappingURL=Router-09b6a7c3.esm.js.map
|