@spacelift-io/backstage-integration-frontend 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/config.d.ts +11 -0
- package/dist/api/SpaceliftApiClient.esm.js +34 -0
- package/dist/api/SpaceliftApiClient.esm.js.map +1 -0
- package/dist/components/SpaceliftStacksTable/SpaceliftStacksTable.esm.js +144 -0
- package/dist/components/SpaceliftStacksTable/SpaceliftStacksTable.esm.js.map +1 -0
- package/dist/components/Stacks/Stacks.esm.js +49 -0
- package/dist/components/Stacks/Stacks.esm.js.map +1 -0
- package/dist/components/Stacks/index.esm.js +2 -0
- package/dist/components/Stacks/index.esm.js.map +1 -0
- package/dist/constants.esm.js +7 -0
- package/dist/constants.esm.js.map +1 -0
- package/dist/hooks/useFetchStacks.esm.js +42 -0
- package/dist/hooks/useFetchStacks.esm.js.map +1 -0
- package/dist/hooks/useTriggerRun.esm.js +29 -0
- package/dist/hooks/useTriggerRun.esm.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +30 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +106 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Spacelift Frontend Plugin
|
|
2
|
+
|
|
3
|
+
This frontend plugin for Backstage provides a user interface to view and interact with your Spacelift stacks and runs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
1. Install the plugin package in your Backstage frontend app:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# From your Backstage root directory
|
|
11
|
+
yarn --cwd packages/app add @spacelift/backstage-integration-frontend
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. Add the plugin to your `packages/app/src/App.tsx`:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// packages/app/src/App.tsx
|
|
18
|
+
import { SpaceliftIoPage } from '@spacelift/backstage-integration-frontend';
|
|
19
|
+
|
|
20
|
+
// ...
|
|
21
|
+
|
|
22
|
+
const routes = (
|
|
23
|
+
<FlatRoutes>
|
|
24
|
+
{/* ...other routes */}
|
|
25
|
+
<Route path="/spacelift" element={<SpaceliftIoPage />} />
|
|
26
|
+
</FlatRoutes>
|
|
27
|
+
);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Add the plugin to the sidebar in `packages/app/src/components/Root/Root.tsx`:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// packages/app/src/components/Root/Root.tsx
|
|
34
|
+
import SpaceliftIcon from '@material-ui/icons/CloudQueue'; // Example icon, choose an appropriate one
|
|
35
|
+
|
|
36
|
+
// ...
|
|
37
|
+
|
|
38
|
+
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
|
39
|
+
<SidebarPage>
|
|
40
|
+
<Sidebar>
|
|
41
|
+
{/* ...other sidebar items */}
|
|
42
|
+
<SidebarItem icon={SpaceliftIcon} to="spacelift" text="Spacelift" />
|
|
43
|
+
</Sidebar>
|
|
44
|
+
{/* ... */}
|
|
45
|
+
</SidebarPage>
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
This plugin requires the `spacelift.hostUrl` to be configured in your `app-config.yaml` to allow the frontend to make requests to the Spacelift API via the backend plugin.
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
spacelift:
|
|
55
|
+
hostUrl: 'https://<your-subdomain>.app.spacelift.io' # Your Spacelift instance URL
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Make sure to replace `<your-subdomain>` with your actual Spacelift subdomain.
|
|
59
|
+
|
|
60
|
+
### Important Note on Permissions
|
|
61
|
+
|
|
62
|
+
This frontend plugin relies on the permissions configured for the Spacelift API Key in the backend plugin. It does not implement separate user-level permission checks within the frontend components.
|
|
63
|
+
|
|
64
|
+
Ensure that your Backstage instance has appropriate general permissions set up to control access to this plugin's pages and functionalities. This is crucial to prevent users from performing actions in Spacelift for which they are not authorized via the configured API key.
|
|
65
|
+
|
|
66
|
+
## Backend Plugin
|
|
67
|
+
|
|
68
|
+
This frontend plugin requires the [Spacelift Backend Plugin](../spacelift-io-backend/README.md) to be installed and configured.
|
|
69
|
+
|
|
70
|
+
## Spacelift Documentation
|
|
71
|
+
|
|
72
|
+
For more information about Spacelift, please refer to the [official Spacelift documentation](https://docs.spacelift.io/).
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createApiRef } from '@backstage/core-plugin-api';
|
|
2
|
+
|
|
3
|
+
const spaceliftApiRef = createApiRef({
|
|
4
|
+
id: "plugin.spacelift.io.service"
|
|
5
|
+
});
|
|
6
|
+
class SpaceliftApi {
|
|
7
|
+
constructor(discoveryApi, fetchApi) {
|
|
8
|
+
this.discoveryApi = discoveryApi;
|
|
9
|
+
this.fetchApi = fetchApi;
|
|
10
|
+
}
|
|
11
|
+
async getStacks() {
|
|
12
|
+
const url = await this.discoveryApi.getBaseUrl("spacelift-io");
|
|
13
|
+
const response = await this.fetchApi.fetch(`${url}/stacks`);
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
const { error } = await response.json();
|
|
16
|
+
throw new Error(error);
|
|
17
|
+
}
|
|
18
|
+
return response.json();
|
|
19
|
+
}
|
|
20
|
+
async triggerRun(stackId) {
|
|
21
|
+
const url = await this.discoveryApi.getBaseUrl("spacelift-io");
|
|
22
|
+
const response = await this.fetchApi.fetch(`${url}/stacks/${stackId}/trigger`, {
|
|
23
|
+
method: "POST"
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const { error } = await response.json();
|
|
27
|
+
throw new Error(error);
|
|
28
|
+
}
|
|
29
|
+
return response.json();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { SpaceliftApi, spaceliftApiRef };
|
|
34
|
+
//# sourceMappingURL=SpaceliftApiClient.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpaceliftApiClient.esm.js","sources":["../../src/api/SpaceliftApiClient.ts"],"sourcesContent":["import { createApiRef, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { Stack, TriggerRunResponse } from '../types';\n\nexport interface ISpaceliftApi {\n getStacks(): Promise<Stack[]>;\n triggerRun(stackId: string): Promise<TriggerRunResponse>;\n}\n\nexport const spaceliftApiRef = createApiRef<ISpaceliftApi>({\n id: 'plugin.spacelift.io.service',\n});\n\nexport class SpaceliftApi implements ISpaceliftApi {\n constructor(private discoveryApi: DiscoveryApi, private fetchApi: FetchApi) {}\n\n async getStacks(): Promise<Stack[]> {\n const url = await this.discoveryApi.getBaseUrl('spacelift-io');\n\n const response = await this.fetchApi.fetch(`${url}/stacks`);\n if (!response.ok) {\n const { error } = await response.json();\n throw new Error(error);\n }\n return response.json();\n }\n\n async triggerRun(stackId: string): Promise<TriggerRunResponse> {\n const url = await this.discoveryApi.getBaseUrl('spacelift-io');\n\n const response = await this.fetchApi.fetch(`${url}/stacks/${stackId}/trigger`, {\n method: 'POST',\n });\n if (!response.ok) {\n const { error } = await response.json();\n throw new Error(error);\n }\n return response.json();\n }\n}\n"],"names":[],"mappings":";;AAQO,MAAM,kBAAkB,YAA4B,CAAA;AAAA,EACzD,EAAI,EAAA;AACN,CAAC;AAEM,MAAM,YAAsC,CAAA;AAAA,EACjD,WAAA,CAAoB,cAAoC,QAAoB,EAAA;AAAxD,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AAAoC,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA;AAAqB,EAE7E,MAAM,SAA8B,GAAA;AAClC,IAAA,MAAM,GAAM,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,cAAc,CAAA;AAE7D,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAM,CAAA,CAAA,EAAG,GAAG,CAAS,OAAA,CAAA,CAAA;AAC1D,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,SAAS,IAAK,EAAA;AACtC,MAAM,MAAA,IAAI,MAAM,KAAK,CAAA;AAAA;AAEvB,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AACvB,EAEA,MAAM,WAAW,OAA8C,EAAA;AAC7D,IAAA,MAAM,GAAM,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,cAAc,CAAA;AAE7D,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,CAAG,EAAA,GAAG,CAAW,QAAA,EAAA,OAAO,CAAY,QAAA,CAAA,EAAA;AAAA,MAC7E,MAAQ,EAAA;AAAA,KACT,CAAA;AACD,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,SAAS,IAAK,EAAA;AACtC,MAAM,MAAA,IAAI,MAAM,KAAK,CAAA;AAAA;AAEvB,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AAEzB;;;;"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Table, MarkdownContent } from '@backstage/core-components';
|
|
3
|
+
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { makeStyles, Drawer, Box, DialogTitle, IconButton, Link, Chip, Tooltip, Typography } from '@material-ui/core';
|
|
5
|
+
import CloseIcon from '@material-ui/icons/Close';
|
|
6
|
+
import RepeatIcon from '@material-ui/icons/RepeatRounded';
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import { DESCRIPTION_DRAWER_WIDTH, ALLOW_RETRY_STATES, DESCRIPTION_TRUNCATE_LENGTH } from '../../constants.esm.js';
|
|
9
|
+
|
|
10
|
+
const useStyles = makeStyles((theme) => ({
|
|
11
|
+
neutral: {
|
|
12
|
+
backgroundColor: theme.palette.grey[500]
|
|
13
|
+
},
|
|
14
|
+
info: {
|
|
15
|
+
backgroundColor: theme.palette.info.main
|
|
16
|
+
},
|
|
17
|
+
success: {
|
|
18
|
+
backgroundColor: theme.palette.success.main
|
|
19
|
+
},
|
|
20
|
+
danger: {
|
|
21
|
+
backgroundColor: theme.palette.error.main
|
|
22
|
+
},
|
|
23
|
+
warning: {
|
|
24
|
+
backgroundColor: theme.palette.warning.main
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
const renderState = (classes, state) => {
|
|
28
|
+
const stateToClass = {
|
|
29
|
+
APPLYING: classes.info,
|
|
30
|
+
CONFIRMED: classes.warning,
|
|
31
|
+
DESTROYING: classes.info,
|
|
32
|
+
DISCARDED: classes.danger,
|
|
33
|
+
FAILED: classes.danger,
|
|
34
|
+
FINISHED: classes.success,
|
|
35
|
+
INITIALIZING: classes.info,
|
|
36
|
+
NONE: classes.neutral,
|
|
37
|
+
PLANNING: classes.info,
|
|
38
|
+
PREPARING_APPLY: classes.info,
|
|
39
|
+
PREPARING_REPLAN: classes.info,
|
|
40
|
+
PREPARING: classes.info,
|
|
41
|
+
REPLAN_REQUESTED: classes.neutral,
|
|
42
|
+
STOPPED: classes.danger,
|
|
43
|
+
UNCONFIRMED: classes.warning
|
|
44
|
+
};
|
|
45
|
+
const className = stateToClass[state] || classes.neutral;
|
|
46
|
+
return /* @__PURE__ */ jsx(Chip, { size: "small", label: state, className });
|
|
47
|
+
};
|
|
48
|
+
const renderDescription = (description, onExpand) => {
|
|
49
|
+
if (!description) return /* @__PURE__ */ jsx("span", { children: "-" });
|
|
50
|
+
return description.length > DESCRIPTION_TRUNCATE_LENGTH ? /* @__PURE__ */ jsxs(Typography, { variant: "inherit", onClick: () => onExpand(description), children: [
|
|
51
|
+
description.slice(0, DESCRIPTION_TRUNCATE_LENGTH),
|
|
52
|
+
" ..."
|
|
53
|
+
] }) : description;
|
|
54
|
+
};
|
|
55
|
+
const SpaceliftStacksTable = ({
|
|
56
|
+
stacks,
|
|
57
|
+
loading,
|
|
58
|
+
triggerRun
|
|
59
|
+
}) => {
|
|
60
|
+
const [description, setDescription] = useState(null);
|
|
61
|
+
const classes = useStyles();
|
|
62
|
+
const config = useApi(configApiRef);
|
|
63
|
+
const hostUrl = config.getString("spacelift.hostUrl");
|
|
64
|
+
const columns = [
|
|
65
|
+
{
|
|
66
|
+
title: "Name",
|
|
67
|
+
field: "name",
|
|
68
|
+
highlight: true,
|
|
69
|
+
render: (row) => /* @__PURE__ */ jsx(Link, { href: `https://${hostUrl}/stack/${row.id}`, target: "_blank", children: row.name })
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
title: "Description",
|
|
73
|
+
field: "description",
|
|
74
|
+
render: (row) => renderDescription(row.description, setDescription)
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: "Labels",
|
|
78
|
+
field: "labels",
|
|
79
|
+
render: (row) => {
|
|
80
|
+
if (!row.labels.length) return /* @__PURE__ */ jsx("span", { children: "-" });
|
|
81
|
+
return /* @__PURE__ */ jsx(Box, { display: "flex", flexWrap: "wrap", children: row.labels.map((label) => /* @__PURE__ */ jsx(Chip, { label, style: { margin: "0.25rem" }, size: "small" }, label)) });
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
title: "Current State",
|
|
86
|
+
render: (row) => renderState(classes, row.state)
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: "Branch",
|
|
90
|
+
field: "branch"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
title: "Space",
|
|
94
|
+
render: (row) => /* @__PURE__ */ jsx(Link, { href: `https://${hostUrl}/spaces/${row.spaceDetails.name}`, target: "_blank", children: row.spaceDetails.name })
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
width: "30px",
|
|
98
|
+
render: (row) => {
|
|
99
|
+
const showAction = ALLOW_RETRY_STATES.includes(row.state);
|
|
100
|
+
if (!showAction) return null;
|
|
101
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: "Trigger run", children: /* @__PURE__ */ jsx(IconButton, { onClick: () => triggerRun(row.id), children: /* @__PURE__ */ jsx(RepeatIcon, {}) }) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
Table,
|
|
108
|
+
{
|
|
109
|
+
title: "Spacelift Stacks",
|
|
110
|
+
options: {
|
|
111
|
+
search: true,
|
|
112
|
+
paging: true,
|
|
113
|
+
pageSize: 20,
|
|
114
|
+
padding: "dense"
|
|
115
|
+
},
|
|
116
|
+
columns,
|
|
117
|
+
data: stacks,
|
|
118
|
+
isLoading: loading,
|
|
119
|
+
emptyContent: "No stacks found"
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ jsxs(Drawer, { anchor: "right", open: !!description, onClose: () => setDescription(null), children: [
|
|
123
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
DialogTitle,
|
|
126
|
+
{
|
|
127
|
+
id: "dialog-title",
|
|
128
|
+
style: {
|
|
129
|
+
padding: "0 1rem",
|
|
130
|
+
display: "flex",
|
|
131
|
+
justifyContent: "space-between"
|
|
132
|
+
},
|
|
133
|
+
children: "Stack description"
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
/* @__PURE__ */ jsx(IconButton, { "aria-label": "close", onClick: () => setDescription(null), children: /* @__PURE__ */ jsx(CloseIcon, {}) })
|
|
137
|
+
] }),
|
|
138
|
+
/* @__PURE__ */ jsx(Box, { width: DESCRIPTION_DRAWER_WIDTH, padding: 2, style: { paddingTop: 0 }, children: /* @__PURE__ */ jsx(MarkdownContent, { content: description ?? "" }) })
|
|
139
|
+
] })
|
|
140
|
+
] });
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export { SpaceliftStacksTable };
|
|
144
|
+
//# sourceMappingURL=SpaceliftStacksTable.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpaceliftStacksTable.esm.js","sources":["../../../src/components/SpaceliftStacksTable/SpaceliftStacksTable.tsx"],"sourcesContent":["import { MarkdownContent, Table, TableColumn } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n Box,\n Chip,\n DialogTitle,\n Drawer,\n IconButton,\n Link,\n makeStyles,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport CloseIcon from '@material-ui/icons/Close';\nimport RepeatIcon from '@material-ui/icons/RepeatRounded';\n\nimport { useState } from 'react';\nimport {\n ALLOW_RETRY_STATES,\n DESCRIPTION_DRAWER_WIDTH,\n DESCRIPTION_TRUNCATE_LENGTH,\n} from '../../constants';\nimport { Stack, StackState } from '../../types';\n\nconst useStyles = makeStyles(theme => ({\n neutral: {\n backgroundColor: theme.palette.grey[500],\n },\n info: {\n backgroundColor: theme.palette.info.main,\n },\n success: {\n backgroundColor: theme.palette.success.main,\n },\n danger: {\n backgroundColor: theme.palette.error.main,\n },\n warning: {\n backgroundColor: theme.palette.warning.main,\n },\n}));\n\n// Helper function to render stack state with appropriate styling\nconst renderState = (classes: ReturnType<typeof useStyles>, state: StackState) => {\n const stateToClass: Record<StackState, string> = {\n APPLYING: classes.info,\n CONFIRMED: classes.warning,\n DESTROYING: classes.info,\n DISCARDED: classes.danger,\n FAILED: classes.danger,\n FINISHED: classes.success,\n INITIALIZING: classes.info,\n NONE: classes.neutral,\n PLANNING: classes.info,\n PREPARING_APPLY: classes.info,\n PREPARING_REPLAN: classes.info,\n PREPARING: classes.info,\n REPLAN_REQUESTED: classes.neutral,\n STOPPED: classes.danger,\n UNCONFIRMED: classes.warning,\n };\n\n const className = stateToClass[state] || classes.neutral;\n return <Chip size=\"small\" label={state} className={className} />;\n};\n\nconst renderDescription = (\n description: string | null | undefined,\n onExpand: (description: string | null) => void\n) => {\n if (!description) return <span>-</span>;\n return description.length > DESCRIPTION_TRUNCATE_LENGTH ? (\n <Typography variant=\"inherit\" onClick={() => onExpand(description)}>\n {description.slice(0, DESCRIPTION_TRUNCATE_LENGTH)} ...\n </Typography>\n ) : (\n description\n );\n};\n\ntype SpaceliftStacksTableProps = {\n stacks: Stack[];\n loading: boolean;\n triggerRun: (stackId: string) => void;\n};\n\nexport const SpaceliftStacksTable = ({\n stacks,\n loading,\n triggerRun,\n}: SpaceliftStacksTableProps) => {\n const [description, setDescription] = useState<string | null>(null);\n const classes = useStyles();\n const config = useApi(configApiRef);\n const hostUrl = config.getString('spacelift.hostUrl');\n\n const columns: TableColumn<Stack>[] = [\n {\n title: 'Name',\n field: 'name',\n highlight: true,\n render: (row: Stack) => (\n <Link href={`https://${hostUrl}/stack/${row.id}`} target=\"_blank\">\n {row.name}\n </Link>\n ),\n },\n {\n title: 'Description',\n field: 'description',\n render: (row: Stack) => renderDescription(row.description, setDescription),\n },\n {\n title: 'Labels',\n field: 'labels',\n render: (row: Stack) => {\n if (!row.labels.length) return <span>-</span>;\n return (\n <Box display=\"flex\" flexWrap=\"wrap\">\n {row.labels.map(label => (\n <Chip key={label} label={label} style={{ margin: '0.25rem' }} size=\"small\" />\n ))}\n </Box>\n );\n },\n },\n {\n title: 'Current State',\n render: (row: Stack) => renderState(classes, row.state),\n },\n {\n title: 'Branch',\n field: 'branch',\n },\n {\n title: 'Space',\n render: (row: Stack) => (\n <Link href={`https://${hostUrl}/spaces/${row.spaceDetails.name}`} target=\"_blank\">\n {row.spaceDetails.name}\n </Link>\n ),\n },\n {\n width: '30px',\n render: (row: Stack) => {\n const showAction = ALLOW_RETRY_STATES.includes(row.state);\n if (!showAction) return null;\n return (\n <Tooltip title=\"Trigger run\">\n <IconButton onClick={() => triggerRun(row.id)}>\n <RepeatIcon />\n </IconButton>\n </Tooltip>\n );\n },\n },\n ];\n\n return (\n <>\n <Table\n title=\"Spacelift Stacks\"\n options={{\n search: true,\n paging: true,\n pageSize: 20,\n padding: 'dense',\n }}\n columns={columns}\n data={stacks}\n isLoading={loading}\n emptyContent=\"No stacks found\"\n />\n <Drawer anchor=\"right\" open={!!description} onClose={() => setDescription(null)}>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <DialogTitle\n id=\"dialog-title\"\n style={{\n padding: '0 1rem',\n display: 'flex',\n justifyContent: 'space-between',\n }}\n >\n Stack description\n </DialogTitle>\n <IconButton aria-label=\"close\" onClick={() => setDescription(null)}>\n <CloseIcon />\n </IconButton>\n </Box>\n <Box width={DESCRIPTION_DRAWER_WIDTH} padding={2} style={{ paddingTop: 0 }}>\n <MarkdownContent content={description ?? ''} />\n </Box>\n </Drawer>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Page, Header, HeaderLabel, Content } from '@backstage/core-components';
|
|
3
|
+
import { Box, Button } from '@material-ui/core';
|
|
4
|
+
import { Alert } from '@material-ui/lab';
|
|
5
|
+
import { useFetchStacks } from '../../hooks/useFetchStacks.esm.js';
|
|
6
|
+
import { useTriggerRun } from '../../hooks/useTriggerRun.esm.js';
|
|
7
|
+
import { SpaceliftStacksTable } from '../SpaceliftStacksTable/SpaceliftStacksTable.esm.js';
|
|
8
|
+
|
|
9
|
+
const StacksPage = () => {
|
|
10
|
+
const { stacks, loading: stacksLoading, error: stacksError, retry: getStacks } = useFetchStacks();
|
|
11
|
+
const {
|
|
12
|
+
triggerRun,
|
|
13
|
+
loading: loadingTriggerRun,
|
|
14
|
+
error: errorTriggerRun,
|
|
15
|
+
clear: clearTriggerRunError
|
|
16
|
+
} = useTriggerRun();
|
|
17
|
+
const handleTriggerRun = async (stackId) => {
|
|
18
|
+
const res = await triggerRun(stackId);
|
|
19
|
+
if (res?.id) {
|
|
20
|
+
getStacks();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const isLoading = stacksLoading || loadingTriggerRun;
|
|
24
|
+
return /* @__PURE__ */ jsxs(Page, { themeId: "tool", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(Header, { title: "Spacelift", subtitle: "Manage your stacks", children: /* @__PURE__ */ jsx(HeaderLabel, { label: "Stacks", value: "List of stacks" }) }),
|
|
26
|
+
/* @__PURE__ */ jsx(Content, { children: /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", style: { gap: "16px" }, children: [
|
|
27
|
+
stacksError && /* @__PURE__ */ jsxs(
|
|
28
|
+
Alert,
|
|
29
|
+
{
|
|
30
|
+
variant: "outlined",
|
|
31
|
+
severity: "error",
|
|
32
|
+
action: /* @__PURE__ */ jsx(Button, { color: "inherit", size: "small", onClick: getStacks, children: "RETRY" }),
|
|
33
|
+
children: [
|
|
34
|
+
"Failed to load Spacelift stacks: ",
|
|
35
|
+
stacksError.message
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
),
|
|
39
|
+
!stacksError && errorTriggerRun && /* @__PURE__ */ jsxs(Alert, { variant: "outlined", severity: "warning", onClose: clearTriggerRunError, children: [
|
|
40
|
+
"Spacelift action failed: ",
|
|
41
|
+
errorTriggerRun.message
|
|
42
|
+
] }),
|
|
43
|
+
/* @__PURE__ */ jsx(SpaceliftStacksTable, { stacks, loading: isLoading, triggerRun: handleTriggerRun })
|
|
44
|
+
] }) })
|
|
45
|
+
] });
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { StacksPage };
|
|
49
|
+
//# sourceMappingURL=Stacks.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Stacks.esm.js","sources":["../../../src/components/Stacks/Stacks.tsx"],"sourcesContent":["import { Content, Header, HeaderLabel, Page } from '@backstage/core-components';\nimport { Box, Button } from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { useFetchStacks } from '../../hooks/useFetchStacks';\nimport { useTriggerRun } from '../../hooks/useTriggerRun';\nimport { SpaceliftStacksTable } from '../SpaceliftStacksTable';\n\nexport const StacksPage = () => {\n const { stacks, loading: stacksLoading, error: stacksError, retry: getStacks } = useFetchStacks();\n const {\n triggerRun,\n loading: loadingTriggerRun,\n error: errorTriggerRun,\n clear: clearTriggerRunError,\n } = useTriggerRun();\n\n const handleTriggerRun = async (stackId: string) => {\n const res = await triggerRun(stackId);\n if (res?.id) {\n getStacks();\n }\n };\n\n const isLoading = stacksLoading || loadingTriggerRun;\n\n return (\n <Page themeId=\"tool\">\n <Header title=\"Spacelift\" subtitle=\"Manage your stacks\">\n <HeaderLabel label=\"Stacks\" value=\"List of stacks\" />\n </Header>\n <Content>\n <Box display=\"flex\" flexDirection=\"column\" style={{ gap: '16px' }}>\n {stacksError && (\n <Alert\n variant=\"outlined\"\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={getStacks}>\n RETRY\n </Button>\n }\n >\n Failed to load Spacelift stacks: {stacksError.message}\n </Alert>\n )}\n {!stacksError && errorTriggerRun && (\n <Alert variant=\"outlined\" severity=\"warning\" onClose={clearTriggerRunError}>\n Spacelift action failed: {errorTriggerRun.message}\n </Alert>\n )}\n <SpaceliftStacksTable stacks={stacks} loading={isLoading} triggerRun={handleTriggerRun} />\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":";;;;;;;;AAOO,MAAM,aAAa,MAAM;AAC9B,EAAM,MAAA,EAAE,QAAQ,OAAS,EAAA,aAAA,EAAe,OAAO,WAAa,EAAA,KAAA,EAAO,SAAU,EAAA,GAAI,cAAe,EAAA;AAChG,EAAM,MAAA;AAAA,IACJ,UAAA;AAAA,IACA,OAAS,EAAA,iBAAA;AAAA,IACT,KAAO,EAAA,eAAA;AAAA,IACP,KAAO,EAAA;AAAA,MACL,aAAc,EAAA;AAElB,EAAM,MAAA,gBAAA,GAAmB,OAAO,OAAoB,KAAA;AAClD,IAAM,MAAA,GAAA,GAAM,MAAM,UAAA,CAAW,OAAO,CAAA;AACpC,IAAA,IAAI,KAAK,EAAI,EAAA;AACX,MAAU,SAAA,EAAA;AAAA;AACZ,GACF;AAEA,EAAA,MAAM,YAAY,aAAiB,IAAA,iBAAA;AAEnC,EACE,uBAAA,IAAA,CAAC,IAAK,EAAA,EAAA,OAAA,EAAQ,MACZ,EAAA,QAAA,EAAA;AAAA,oBAAC,GAAA,CAAA,MAAA,EAAA,EAAO,KAAM,EAAA,WAAA,EAAY,QAAS,EAAA,oBAAA,EACjC,QAAC,kBAAA,GAAA,CAAA,WAAA,EAAA,EAAY,KAAM,EAAA,QAAA,EAAS,KAAM,EAAA,gBAAA,EAAiB,CACrD,EAAA,CAAA;AAAA,oBACC,GAAA,CAAA,OAAA,EAAA,EACC,QAAC,kBAAA,IAAA,CAAA,GAAA,EAAA,EAAI,OAAQ,EAAA,MAAA,EAAO,aAAc,EAAA,QAAA,EAAS,KAAO,EAAA,EAAE,GAAK,EAAA,MAAA,EACtD,EAAA,QAAA,EAAA;AAAA,MACC,WAAA,oBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,OAAQ,EAAA,UAAA;AAAA,UACR,QAAS,EAAA,OAAA;AAAA,UACT,MAAA,sBACG,MAAO,EAAA,EAAA,KAAA,EAAM,WAAU,IAAK,EAAA,OAAA,EAAQ,OAAS,EAAA,SAAA,EAAW,QAEzD,EAAA,OAAA,EAAA,CAAA;AAAA,UAEH,QAAA,EAAA;AAAA,YAAA,mCAAA;AAAA,YACmC,WAAY,CAAA;AAAA;AAAA;AAAA,OAChD;AAAA,MAED,CAAC,WAAe,IAAA,eAAA,oBACd,IAAA,CAAA,KAAA,EAAA,EAAM,SAAQ,UAAW,EAAA,QAAA,EAAS,SAAU,EAAA,OAAA,EAAS,oBAAsB,EAAA,QAAA,EAAA;AAAA,QAAA,2BAAA;AAAA,QAChD,eAAgB,CAAA;AAAA,OAC5C,EAAA,CAAA;AAAA,0BAED,oBAAqB,EAAA,EAAA,MAAA,EAAgB,OAAS,EAAA,SAAA,EAAW,YAAY,gBAAkB,EAAA;AAAA,KAAA,EAC1F,CACF,EAAA;AAAA,GACF,EAAA,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const POLL_INTERVAL = 1e4;
|
|
2
|
+
const ALLOW_RETRY_STATES = ["DISCARDED", "FAILED", "STOPPED", "FINISHED", "NONE"];
|
|
3
|
+
const DESCRIPTION_TRUNCATE_LENGTH = 50;
|
|
4
|
+
const DESCRIPTION_DRAWER_WIDTH = 400;
|
|
5
|
+
|
|
6
|
+
export { ALLOW_RETRY_STATES, DESCRIPTION_DRAWER_WIDTH, DESCRIPTION_TRUNCATE_LENGTH, POLL_INTERVAL };
|
|
7
|
+
//# sourceMappingURL=constants.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.esm.js","sources":["../src/constants.ts"],"sourcesContent":["// Spacelift Plugin Constants\n\n// For useFetchStacks.ts\nexport const POLL_INTERVAL = 10000; // 10 seconds\n\n// For SpaceliftStacksTable.tsx\nexport const ALLOW_RETRY_STATES = ['DISCARDED', 'FAILED', 'STOPPED', 'FINISHED', 'NONE'];\n\nexport const DESCRIPTION_TRUNCATE_LENGTH = 50;\nexport const DESCRIPTION_DRAWER_WIDTH = 400;\n"],"names":[],"mappings":"AAGO,MAAM,aAAgB,GAAA;AAGtB,MAAM,qBAAqB,CAAC,WAAA,EAAa,QAAU,EAAA,SAAA,EAAW,YAAY,MAAM;AAEhF,MAAM,2BAA8B,GAAA;AACpC,MAAM,wBAA2B,GAAA;;;;"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
2
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
3
|
+
import { spaceliftApiRef } from '../api/SpaceliftApiClient.esm.js';
|
|
4
|
+
import { POLL_INTERVAL } from '../constants.esm.js';
|
|
5
|
+
|
|
6
|
+
const useFetchStacks = () => {
|
|
7
|
+
const spaceliftApi = useApi(spaceliftApiRef);
|
|
8
|
+
const [stacks, setStacks] = useState([]);
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const fetchStacks = useCallback(async () => {
|
|
12
|
+
try {
|
|
13
|
+
const response = await spaceliftApi.getStacks();
|
|
14
|
+
setStacks(response);
|
|
15
|
+
setError(null);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
setError(err);
|
|
18
|
+
setStacks([]);
|
|
19
|
+
} finally {
|
|
20
|
+
setLoading(false);
|
|
21
|
+
}
|
|
22
|
+
}, [spaceliftApi]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const poll = setInterval(() => {
|
|
25
|
+
fetchStacks();
|
|
26
|
+
}, POLL_INTERVAL);
|
|
27
|
+
fetchStacks();
|
|
28
|
+
return () => {
|
|
29
|
+
clearInterval(poll);
|
|
30
|
+
};
|
|
31
|
+
}, [fetchStacks]);
|
|
32
|
+
return {
|
|
33
|
+
stacks,
|
|
34
|
+
loading,
|
|
35
|
+
error,
|
|
36
|
+
retry: fetchStacks,
|
|
37
|
+
clear: () => setError(null)
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { useFetchStacks };
|
|
42
|
+
//# sourceMappingURL=useFetchStacks.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFetchStacks.esm.js","sources":["../../src/hooks/useFetchStacks.ts"],"sourcesContent":["import { useApi } from '@backstage/core-plugin-api';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { ISpaceliftApi, spaceliftApiRef } from '../api/SpaceliftApiClient';\nimport { POLL_INTERVAL } from '../constants';\nimport { Stack } from '../types';\n\nexport const useFetchStacks = () => {\n const spaceliftApi = useApi<ISpaceliftApi>(spaceliftApiRef);\n const [stacks, setStacks] = useState<Stack[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchStacks = useCallback(async () => {\n try {\n const response = await spaceliftApi.getStacks();\n setStacks(response);\n setError(null);\n } catch (err) {\n setError(err as Error);\n setStacks([]);\n } finally {\n setLoading(false);\n }\n }, [spaceliftApi]);\n\n useEffect(() => {\n const poll = setInterval(() => {\n fetchStacks();\n }, POLL_INTERVAL);\n\n fetchStacks();\n\n return () => {\n clearInterval(poll);\n };\n }, [fetchStacks]);\n\n return {\n stacks,\n loading,\n error,\n retry: fetchStacks,\n clear: () => setError(null),\n };\n};\n"],"names":[],"mappings":";;;;;AAOO,MAAM,iBAAiB,MAAM;AAClC,EAAM,MAAA,YAAA,GAAe,OAAsB,eAAe,CAAA;AAC1D,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,QAAA,CAAkB,EAAE,CAAA;AAChD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAM,MAAA,WAAA,GAAc,YAAY,YAAY;AAC1C,IAAI,IAAA;AACF,MAAM,MAAA,QAAA,GAAW,MAAM,YAAA,CAAa,SAAU,EAAA;AAC9C,MAAA,SAAA,CAAU,QAAQ,CAAA;AAClB,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,aACN,GAAK,EAAA;AACZ,MAAA,QAAA,CAAS,GAAY,CAAA;AACrB,MAAA,SAAA,CAAU,EAAE,CAAA;AAAA,KACZ,SAAA;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB,GACF,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,SAAA,CAAU,MAAM;AACd,IAAM,MAAA,IAAA,GAAO,YAAY,MAAM;AAC7B,MAAY,WAAA,EAAA;AAAA,OACX,aAAa,CAAA;AAEhB,IAAY,WAAA,EAAA;AAEZ,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,IAAI,CAAA;AAAA,KACpB;AAAA,GACF,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAO,OAAA;AAAA,IACL,MAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAO,EAAA,WAAA;AAAA,IACP,KAAA,EAAO,MAAM,QAAA,CAAS,IAAI;AAAA,GAC5B;AACF;;;;"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { spaceliftApiRef } from '../api/SpaceliftApiClient.esm.js';
|
|
4
|
+
|
|
5
|
+
const useTriggerRun = () => {
|
|
6
|
+
const [loading, setLoading] = useState(false);
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
const spaceliftApi = useApi(spaceliftApiRef);
|
|
9
|
+
const triggerRun = useCallback(
|
|
10
|
+
async (stackId) => {
|
|
11
|
+
setLoading(true);
|
|
12
|
+
setError(null);
|
|
13
|
+
try {
|
|
14
|
+
const response = await spaceliftApi.triggerRun(stackId);
|
|
15
|
+
return response;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
setError(err);
|
|
18
|
+
return null;
|
|
19
|
+
} finally {
|
|
20
|
+
setLoading(false);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
[spaceliftApi]
|
|
24
|
+
);
|
|
25
|
+
return { triggerRun, loading, error, clear: () => setError(null) };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { useTriggerRun };
|
|
29
|
+
//# sourceMappingURL=useTriggerRun.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTriggerRun.esm.js","sources":["../../src/hooks/useTriggerRun.ts"],"sourcesContent":["import { useApi } from '@backstage/core-plugin-api';\nimport { useCallback, useState } from 'react';\nimport { ISpaceliftApi, spaceliftApiRef } from '../api/SpaceliftApiClient';\n\nexport const useTriggerRun = () => {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const spaceliftApi = useApi<ISpaceliftApi>(spaceliftApiRef);\n\n const triggerRun = useCallback(\n async (stackId: string) => {\n setLoading(true);\n setError(null);\n try {\n const response = await spaceliftApi.triggerRun(stackId);\n return response;\n } catch (err) {\n setError(err as Error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [spaceliftApi]\n );\n\n return { triggerRun, loading, error, clear: () => setError(null) };\n};\n"],"names":[],"mappings":";;;;AAIO,MAAM,gBAAgB,MAAM;AACjC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAM,MAAA,YAAA,GAAe,OAAsB,eAAe,CAAA;AAE1D,EAAA,MAAM,UAAa,GAAA,WAAA;AAAA,IACjB,OAAO,OAAoB,KAAA;AACzB,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAI,IAAA;AACF,QAAA,MAAM,QAAW,GAAA,MAAM,YAAa,CAAA,UAAA,CAAW,OAAO,CAAA;AACtD,QAAO,OAAA,QAAA;AAAA,eACA,GAAK,EAAA;AACZ,QAAA,QAAA,CAAS,GAAY,CAAA;AACrB,QAAO,OAAA,IAAA;AAAA,OACP,SAAA;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB,KACF;AAAA,IACA,CAAC,YAAY;AAAA,GACf;AAEA,EAAO,OAAA,EAAE,YAAY,OAAS,EAAA,KAAA,EAAO,OAAO,MAAM,QAAA,CAAS,IAAI,CAAE,EAAA;AACnE;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
3
|
+
|
|
4
|
+
declare const spaceliftIoPlugin: _backstage_core_plugin_api.BackstagePlugin<{
|
|
5
|
+
root: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
6
|
+
}, {}, {}>;
|
|
7
|
+
declare const SpaceliftIoPage: () => react_jsx_runtime.JSX.Element;
|
|
8
|
+
|
|
9
|
+
export { SpaceliftIoPage, spaceliftIoPlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createPlugin, createApiFactory, fetchApiRef, discoveryApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
|
|
2
|
+
import { SpaceliftApi, spaceliftApiRef } from './api/SpaceliftApiClient.esm.js';
|
|
3
|
+
import { rootRouteRef } from './routes.esm.js';
|
|
4
|
+
|
|
5
|
+
const spaceliftIoPlugin = createPlugin({
|
|
6
|
+
id: "spacelift",
|
|
7
|
+
routes: {
|
|
8
|
+
root: rootRouteRef
|
|
9
|
+
},
|
|
10
|
+
apis: [
|
|
11
|
+
createApiFactory({
|
|
12
|
+
api: spaceliftApiRef,
|
|
13
|
+
deps: {
|
|
14
|
+
discoveryApi: discoveryApiRef,
|
|
15
|
+
fetchApi: fetchApiRef
|
|
16
|
+
},
|
|
17
|
+
factory: ({ discoveryApi, fetchApi }) => new SpaceliftApi(discoveryApi, fetchApi)
|
|
18
|
+
})
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
const SpaceliftIoPage = spaceliftIoPlugin.provide(
|
|
22
|
+
createRoutableExtension({
|
|
23
|
+
name: "SpaceliftIoPage",
|
|
24
|
+
component: () => import('./components/Stacks/index.esm.js').then((m) => m.StacksPage),
|
|
25
|
+
mountPoint: rootRouteRef
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export { SpaceliftIoPage, spaceliftIoPlugin };
|
|
30
|
+
//# sourceMappingURL=plugin.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { SpaceliftApi, spaceliftApiRef } from './api/SpaceliftApiClient';\nimport { rootRouteRef } from './routes';\n\nexport const spaceliftIoPlugin = createPlugin({\n id: 'spacelift',\n routes: {\n root: rootRouteRef,\n },\n apis: [\n createApiFactory({\n api: spaceliftApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: ({ discoveryApi, fetchApi }) => new SpaceliftApi(discoveryApi, fetchApi),\n }),\n ],\n});\n\nexport const SpaceliftIoPage = spaceliftIoPlugin.provide(\n createRoutableExtension({\n name: 'SpaceliftIoPage',\n component: () => import('./components/Stacks').then(m => m.StacksPage),\n mountPoint: rootRouteRef,\n })\n);\n"],"names":[],"mappings":";;;;AAWO,MAAM,oBAAoB,YAAa,CAAA;AAAA,EAC5C,EAAI,EAAA,WAAA;AAAA,EACJ,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA;AAAA,GACR;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,eAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,QAAU,EAAA;AAAA,OACZ;AAAA,MACA,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,UAAe,KAAA,IAAI,YAAa,CAAA,YAAA,EAAc,QAAQ;AAAA,KACjF;AAAA;AAEL,CAAC;AAEM,MAAM,kBAAkB,iBAAkB,CAAA,OAAA;AAAA,EAC/C,uBAAwB,CAAA;AAAA,IACtB,IAAM,EAAA,iBAAA;AAAA,IACN,SAAA,EAAW,MAAM,OAAO,kCAAqB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,UAAU,CAAA;AAAA,IACrE,UAAY,EAAA;AAAA,GACb;AACH;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'spacelift',\n});\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAe,CAAA;AAAA,EACzC,EAAI,EAAA;AACN,CAAC;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spacelift-io/backstage-integration-frontend",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Backstage plugin for integrating Spacelift.io with Backstage",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"backstage-plugin",
|
|
7
|
+
"backstage",
|
|
8
|
+
"spacelift",
|
|
9
|
+
"infrastructure-as-code",
|
|
10
|
+
"IaC",
|
|
11
|
+
"devops",
|
|
12
|
+
"cloud",
|
|
13
|
+
"category/cloud",
|
|
14
|
+
"category/infrastructure"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://spacelift.io",
|
|
17
|
+
"repository": "https://github.com/spacelift-io/backstage-plugins.git",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"main": "dist/index.esm.js",
|
|
20
|
+
"types": "dist/index.d.ts",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"main": "dist/index.esm.js",
|
|
24
|
+
"types": "dist/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"backstage": {
|
|
27
|
+
"role": "frontend-plugin",
|
|
28
|
+
"pluginId": "spacelift-io-frontend",
|
|
29
|
+
"pluginPackages": [
|
|
30
|
+
"@spacelift-io/backstage-integration-frontend"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"configSchema": "config.d.ts",
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"scripts": {
|
|
36
|
+
"start": "backstage-cli package start",
|
|
37
|
+
"build:types": "tsc -b",
|
|
38
|
+
"build": "yarn build:types && backstage-cli package build",
|
|
39
|
+
"lint": "backstage-cli package lint",
|
|
40
|
+
"test": "backstage-cli package test --coverage",
|
|
41
|
+
"test:watch": "backstage-cli package test --watch",
|
|
42
|
+
"clean": "backstage-cli package clean",
|
|
43
|
+
"prepack": "backstage-cli package prepack",
|
|
44
|
+
"postpack": "backstage-cli package postpack"
|
|
45
|
+
},
|
|
46
|
+
"jest": {
|
|
47
|
+
"collectCoverageFrom": [
|
|
48
|
+
"**/*.{ts,tsx}",
|
|
49
|
+
"!constants.ts",
|
|
50
|
+
"!plugin.ts",
|
|
51
|
+
"!routes.ts",
|
|
52
|
+
"!**/mocks/**"
|
|
53
|
+
],
|
|
54
|
+
"coverageThreshold": {
|
|
55
|
+
"global": {
|
|
56
|
+
"branches": 90,
|
|
57
|
+
"functions": 90,
|
|
58
|
+
"lines": 90,
|
|
59
|
+
"statements": 90
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@backstage/core-components": "^0.17.1",
|
|
65
|
+
"@backstage/core-plugin-api": "^1.10.6",
|
|
66
|
+
"@backstage/plugin-catalog-react": "^1.17.0",
|
|
67
|
+
"@backstage/theme": "^0.6.5",
|
|
68
|
+
"@material-ui/core": "^4.9.13",
|
|
69
|
+
"@material-ui/icons": "^4.9.1",
|
|
70
|
+
"@material-ui/lab": "^4.0.0-alpha.61",
|
|
71
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
72
|
+
"luxon": "^3.6.1",
|
|
73
|
+
"react": "^18.0.0",
|
|
74
|
+
"react-dom": "^18.0.0",
|
|
75
|
+
"react-use": "^17.2.4"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@backstage/cli": "^0.32.0",
|
|
82
|
+
"@backstage/core-app-api": "^1.16.1",
|
|
83
|
+
"@backstage/dev-utils": "^1.1.9",
|
|
84
|
+
"@backstage/test-utils": "^1.7.8",
|
|
85
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
86
|
+
"@testing-library/react": "^14.0.0",
|
|
87
|
+
"@testing-library/user-event": "^14.0.0",
|
|
88
|
+
"@types/luxon": "^3",
|
|
89
|
+
"@types/react": "^18.0.0",
|
|
90
|
+
"@types/react-dom": "^18.0.0",
|
|
91
|
+
"msw": "^1.0.0",
|
|
92
|
+
"react-router-dom": "^6.22.0"
|
|
93
|
+
},
|
|
94
|
+
"files": [
|
|
95
|
+
"config.d.ts",
|
|
96
|
+
"dist"
|
|
97
|
+
],
|
|
98
|
+
"typesVersions": {
|
|
99
|
+
"*": {
|
|
100
|
+
"package.json": [
|
|
101
|
+
"package.json"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"module": "./dist/index.esm.js"
|
|
106
|
+
}
|