@backstage-community/plugin-quay 1.21.1 → 1.22.1

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  ### Dependencies
2
2
 
3
+ ## 1.22.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 19fe3ed: Aligned `formatDate` utility with `ADR012 using Luxon`.
8
+
9
+ ## 1.22.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 0341770: Add more context for debugging access issues with Quay
14
+
3
15
  ## 1.21.1
4
16
 
5
17
  ### Patch Changes
@@ -1,14 +1,15 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { Progress, Table, Link } from '@backstage/core-components';
3
3
  import { useApi, configApiRef } from '@backstage/core-plugin-api';
4
+ import { Box, Typography } from '@material-ui/core';
5
+ import { DOC_LINKS } from '../../doc-links.esm.js';
4
6
  import { useRepository, useTags } from '../../hooks/quay.esm.js';
5
7
  import { useQuayViewPermission } from '../../hooks/useQuayViewPermission.esm.js';
6
8
  import PermissionAlert from '../PermissionAlert/PermissionAlert.esm.js';
7
- import { useStyles, columns } from './tableHeading.esm.js';
9
+ import { columns } from './tableHeading.esm.js';
8
10
 
9
11
  function QuayRepository(_props) {
10
12
  const { repository, organization } = useRepository();
11
- const classes = useStyles();
12
13
  const configApi = useApi(configApiRef);
13
14
  const quayUiUrl = configApi.getOptionalString("quay.apiUrl") ?? configApi.getOptionalString("quay.uiUrl");
14
15
  const hasViewPermission = useQuayViewPermission();
@@ -36,7 +37,61 @@ function QuayRepository(_props) {
36
37
  options: { sorting: true, paging: true, padding: "dense" },
37
38
  data,
38
39
  columns,
39
- emptyContent: /* @__PURE__ */ jsx("div", { "data-testid": "quay-repo-table-empty", className: classes.empty, children: "There are no images available." })
40
+ emptyContent: /* @__PURE__ */ jsxs(Box, { "data-testid": "quay-repo-table-empty", padding: 2, children: [
41
+ /* @__PURE__ */ jsx(Typography, { component: "h3", align: "center", variant: "h6", gutterBottom: true, children: "No container images found" }),
42
+ /* @__PURE__ */ jsx(
43
+ Typography,
44
+ {
45
+ component: "p",
46
+ align: "center",
47
+ variant: "body1",
48
+ color: "textSecondary",
49
+ gutterBottom: true,
50
+ children: "This repository doesn't contain any images yet, or there might be an access issue."
51
+ }
52
+ ),
53
+ /* @__PURE__ */ jsxs(Box, { mt: 2, children: [
54
+ /* @__PURE__ */ jsx(
55
+ Typography,
56
+ {
57
+ component: "p",
58
+ align: "center",
59
+ variant: "body2",
60
+ gutterBottom: true,
61
+ children: /* @__PURE__ */ jsx("strong", { children: "Possible solutions:" })
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsx(
65
+ Typography,
66
+ {
67
+ component: "p",
68
+ align: "center",
69
+ variant: "body2",
70
+ gutterBottom: true,
71
+ children: "1. Check if images have been pushed to this repository"
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsx(
75
+ Typography,
76
+ {
77
+ component: "p",
78
+ align: "center",
79
+ variant: "body2",
80
+ gutterBottom: true,
81
+ children: "2. Review the application logs in your Backstage instance"
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsxs(Typography, { component: "p", align: "center", variant: "body2", children: [
85
+ "3. Verify your",
86
+ " ",
87
+ /* @__PURE__ */ jsx(Link, { to: DOC_LINKS.AUTH_TOKEN_GUIDE, children: "Quay access tokens" }),
88
+ " ",
89
+ "are",
90
+ " ",
91
+ /* @__PURE__ */ jsx(Link, { to: DOC_LINKS.BACKEND_CONFIGURATION_GUIDE, children: "configured correctly" })
92
+ ] })
93
+ ] })
94
+ ] })
40
95
  }
41
96
  ) });
42
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"QuayRepository.esm.js","sources":["../../../src/components/QuayRepository/QuayRepository.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Link, Progress, Table } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport { useRepository, useTags } from '../../hooks';\nimport { useQuayViewPermission } from '../../hooks/useQuayViewPermission';\nimport PermissionAlert from '../PermissionAlert/PermissionAlert';\nimport { columns, useStyles } from './tableHeading';\n\ntype QuayRepositoryProps = Record<never, any>;\n\nexport function QuayRepository(_props: QuayRepositoryProps) {\n const { repository, organization } = useRepository();\n const classes = useStyles();\n const configApi = useApi(configApiRef);\n const quayUiUrl =\n configApi.getOptionalString('quay.apiUrl') ??\n configApi.getOptionalString('quay.uiUrl');\n\n const hasViewPermission = useQuayViewPermission();\n\n const title = quayUiUrl ? (\n <>\n {`Quay repository: `}\n <Link\n to={`${quayUiUrl}/repository/${organization}/${repository}`}\n >{`${organization}/${repository}`}</Link>\n </>\n ) : (\n `Quay repository: ${organization}/${repository}`\n );\n const { loading, data } = useTags(organization, repository);\n\n if (!hasViewPermission) {\n return <PermissionAlert />;\n }\n\n if (loading) {\n return (\n <div data-testid=\"quay-repo-progress\">\n <Progress />\n </div>\n );\n }\n\n return (\n <div data-testid=\"quay-repo-table\">\n <Table\n title={title}\n options={{ sorting: true, paging: true, padding: 'dense' }}\n data={data}\n columns={columns}\n emptyContent={\n <div data-testid=\"quay-repo-table-empty\" className={classes.empty}>\n There are no images available.\n </div>\n }\n />\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAyBO,SAAS,eAAe,MAA6B,EAAA;AAC1D,EAAA,MAAM,EAAE,UAAA,EAAY,YAAa,EAAA,GAAI,aAAc,EAAA;AACnD,EAAA,MAAM,UAAU,SAAU,EAAA;AAC1B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAA,MAAM,YACJ,SAAU,CAAA,iBAAA,CAAkB,aAAa,CACzC,IAAA,SAAA,CAAU,kBAAkB,YAAY,CAAA;AAE1C,EAAA,MAAM,oBAAoB,qBAAsB,EAAA;AAEhD,EAAM,MAAA,KAAA,GAAQ,4BAET,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,IAAA,CAAA,iBAAA,CAAA;AAAA,oBACD,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,IAAI,CAAG,EAAA,SAAS,CAAe,YAAA,EAAA,YAAY,IAAI,UAAU,CAAA,CAAA;AAAA,QACzD,QAAA,EAAA,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,UAAU,CAAA;AAAA;AAAA;AAAG,GAAA,EACpC,CAEA,GAAA,CAAA,iBAAA,EAAoB,YAAY,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA;AAEhD,EAAA,MAAM,EAAE,OAAS,EAAA,IAAA,EAAS,GAAA,OAAA,CAAQ,cAAc,UAAU,CAAA;AAE1D,EAAA,IAAI,CAAC,iBAAmB,EAAA;AACtB,IAAA,2BAAQ,eAAgB,EAAA,EAAA,CAAA;AAAA;AAG1B,EAAA,IAAI,OAAS,EAAA;AACX,IAAA,2BACG,KAAI,EAAA,EAAA,aAAA,EAAY,oBACf,EAAA,QAAA,kBAAA,GAAA,CAAC,YAAS,CACZ,EAAA,CAAA;AAAA;AAIJ,EACE,uBAAA,GAAA,CAAC,KAAI,EAAA,EAAA,aAAA,EAAY,iBACf,EAAA,QAAA,kBAAA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA;AAAA,MACA,SAAS,EAAE,OAAA,EAAS,MAAM,MAAQ,EAAA,IAAA,EAAM,SAAS,OAAQ,EAAA;AAAA,MACzD,IAAA;AAAA,MACA,OAAA;AAAA,MACA,YAAA,sBACG,KAAI,EAAA,EAAA,aAAA,EAAY,yBAAwB,SAAW,EAAA,OAAA,CAAQ,OAAO,QAEnE,EAAA,gCAAA,EAAA;AAAA;AAAA,GAGN,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"QuayRepository.esm.js","sources":["../../../src/components/QuayRepository/QuayRepository.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Link, Progress, Table } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\n\nimport { Box, Typography } from '@material-ui/core';\n\nimport { DOC_LINKS } from '../../doc-links';\nimport { useRepository, useTags } from '../../hooks';\nimport { useQuayViewPermission } from '../../hooks/useQuayViewPermission';\nimport PermissionAlert from '../PermissionAlert/PermissionAlert';\nimport { columns } from './tableHeading';\n\ntype QuayRepositoryProps = Record<never, any>;\n\nexport function QuayRepository(_props: QuayRepositoryProps) {\n const { repository, organization } = useRepository();\n const configApi = useApi(configApiRef);\n const quayUiUrl =\n configApi.getOptionalString('quay.apiUrl') ??\n configApi.getOptionalString('quay.uiUrl');\n\n const hasViewPermission = useQuayViewPermission();\n\n const title = quayUiUrl ? (\n <>\n {`Quay repository: `}\n <Link\n to={`${quayUiUrl}/repository/${organization}/${repository}`}\n >{`${organization}/${repository}`}</Link>\n </>\n ) : (\n `Quay repository: ${organization}/${repository}`\n );\n const { loading, data } = useTags(organization, repository);\n\n if (!hasViewPermission) {\n return <PermissionAlert />;\n }\n\n if (loading) {\n return (\n <div data-testid=\"quay-repo-progress\">\n <Progress />\n </div>\n );\n }\n\n return (\n <div data-testid=\"quay-repo-table\">\n <Table\n title={title}\n options={{ sorting: true, paging: true, padding: 'dense' }}\n data={data}\n columns={columns}\n emptyContent={\n <Box data-testid=\"quay-repo-table-empty\" padding={2}>\n <Typography component=\"h3\" align=\"center\" variant=\"h6\" gutterBottom>\n No container images found\n </Typography>\n <Typography\n component=\"p\"\n align=\"center\"\n variant=\"body1\"\n color=\"textSecondary\"\n gutterBottom\n >\n This repository doesn't contain any images yet, or there might be\n an access issue.\n </Typography>\n <Box mt={2}>\n <Typography\n component=\"p\"\n align=\"center\"\n variant=\"body2\"\n gutterBottom\n >\n <strong>Possible solutions:</strong>\n </Typography>\n <Typography\n component=\"p\"\n align=\"center\"\n variant=\"body2\"\n gutterBottom\n >\n 1. Check if images have been pushed to this repository\n </Typography>\n <Typography\n component=\"p\"\n align=\"center\"\n variant=\"body2\"\n gutterBottom\n >\n 2. Review the application logs in your Backstage instance\n </Typography>\n <Typography component=\"p\" align=\"center\" variant=\"body2\">\n 3. Verify your{' '}\n <Link to={DOC_LINKS.AUTH_TOKEN_GUIDE}>Quay access tokens</Link>{' '}\n are{' '}\n <Link to={DOC_LINKS.BACKEND_CONFIGURATION_GUIDE}>\n configured correctly\n </Link>\n </Typography>\n </Box>\n </Box>\n }\n />\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -62,7 +62,7 @@ const columns = [
62
62
  customSort: (a, b) => a.manifest_digest_raw.localeCompare(b.manifest_digest_raw)
63
63
  }
64
64
  ];
65
- const useStyles = makeStyles((theme) => ({
65
+ makeStyles((theme) => ({
66
66
  empty: {
67
67
  padding: theme.spacing(2),
68
68
  display: "flex",
@@ -70,5 +70,5 @@ const useStyles = makeStyles((theme) => ({
70
70
  }
71
71
  }));
72
72
 
73
- export { columns, useStyles };
73
+ export { columns };
74
74
  //# sourceMappingURL=tableHeading.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tableHeading.esm.js","sources":["../../../src/components/QuayRepository/tableHeading.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ReactNode } from 'react';\n\nimport { Link, Progress, TableColumn } from '@backstage/core-components';\n\nimport { Tooltip } from '@material-ui/core';\nimport makeStyles from '@material-ui/core/styles/makeStyles';\n\nimport { securityScanComparator, vulnerabilitySummary } from '../../lib/utils';\nimport type { QuayTagData } from '../../types';\n\nexport const columns: TableColumn<QuayTagData>[] = [\n {\n title: 'Tag',\n field: 'name',\n type: 'string',\n highlight: true,\n },\n {\n title: 'Last Modified',\n field: 'last_modified',\n type: 'date',\n },\n {\n title: 'Security Scan',\n field: 'securityScan',\n render: (rowData: QuayTagData): ReactNode => {\n if (!rowData.securityStatus && !rowData.securityDetails) {\n return (\n <span data-testid=\"quay-repo-security-scan-progress\">\n <Progress />\n </span>\n );\n }\n\n if (rowData.securityStatus === 'queued') {\n return (\n <Tooltip title=\"The manifest for this tag is queued to be scanned for vulnerabilities\">\n <span data-testid=\"quay-repo-queued-for-scan\">Queued</span>\n </Tooltip>\n );\n }\n\n if (rowData.securityStatus === 'unsupported') {\n return (\n <Tooltip title=\"The manifest for this tag has an operating system or package manager unsupported by Quay Security Scanner\">\n <span data-testid=\"quay-repo-security-scan-unsupported\">\n Unsupported\n </span>\n </Tooltip>\n );\n }\n\n const tagManifest = rowData.manifest_digest_raw;\n const retStr = vulnerabilitySummary(rowData.securityDetails);\n return (\n <Link\n data-testid={`${rowData.name}-security-scan`}\n to={`tag/${tagManifest}`}\n >\n {retStr}\n </Link>\n );\n },\n id: 'securityScan',\n customSort: (a: QuayTagData, b: QuayTagData) =>\n securityScanComparator(a, b),\n },\n {\n title: 'Size',\n field: 'size',\n type: 'numeric',\n customSort: (a: QuayTagData, b: QuayTagData) => a.rawSize - b.rawSize,\n },\n {\n title: 'Expires',\n field: 'expiration',\n type: 'date',\n emptyValue: <i>Never</i>,\n },\n {\n title: 'Manifest',\n field: 'manifest_digest',\n type: 'string',\n customSort: (a: QuayTagData, b: QuayTagData) =>\n a.manifest_digest_raw.localeCompare(b.manifest_digest_raw),\n },\n];\n\nexport const useStyles = makeStyles(theme => ({\n empty: {\n padding: theme.spacing(2),\n display: 'flex',\n justifyContent: 'center',\n },\n}));\n"],"names":[],"mappings":";;;;;;AAyBO,MAAM,OAAsC,GAAA;AAAA,EACjD;AAAA,IACE,KAAO,EAAA,KAAA;AAAA,IACP,KAAO,EAAA,MAAA;AAAA,IACP,IAAM,EAAA,QAAA;AAAA,IACN,SAAW,EAAA;AAAA,GACb;AAAA,EACA;AAAA,IACE,KAAO,EAAA,eAAA;AAAA,IACP,KAAO,EAAA,eAAA;AAAA,IACP,IAAM,EAAA;AAAA,GACR;AAAA,EACA;AAAA,IACE,KAAO,EAAA,eAAA;AAAA,IACP,KAAO,EAAA,cAAA;AAAA,IACP,MAAA,EAAQ,CAAC,OAAoC,KAAA;AAC3C,MAAA,IAAI,CAAC,OAAA,CAAQ,cAAkB,IAAA,CAAC,QAAQ,eAAiB,EAAA;AACvD,QAAA,2BACG,MAAK,EAAA,EAAA,aAAA,EAAY,kCAChB,EAAA,QAAA,kBAAA,GAAA,CAAC,YAAS,CACZ,EAAA,CAAA;AAAA;AAIJ,MAAI,IAAA,OAAA,CAAQ,mBAAmB,QAAU,EAAA;AACvC,QACE,uBAAA,GAAA,CAAC,WAAQ,KAAM,EAAA,uEAAA,EACb,8BAAC,MAAK,EAAA,EAAA,aAAA,EAAY,2BAA4B,EAAA,QAAA,EAAA,QAAA,EAAM,CACtD,EAAA,CAAA;AAAA;AAIJ,MAAI,IAAA,OAAA,CAAQ,mBAAmB,aAAe,EAAA;AAC5C,QACE,uBAAA,GAAA,CAAC,WAAQ,KAAM,EAAA,2GAAA,EACb,8BAAC,MAAK,EAAA,EAAA,aAAA,EAAY,qCAAsC,EAAA,QAAA,EAAA,aAAA,EAExD,CACF,EAAA,CAAA;AAAA;AAIJ,MAAA,MAAM,cAAc,OAAQ,CAAA,mBAAA;AAC5B,MAAM,MAAA,MAAA,GAAS,oBAAqB,CAAA,OAAA,CAAQ,eAAe,CAAA;AAC3D,MACE,uBAAA,GAAA;AAAA,QAAC,IAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAa,CAAG,EAAA,OAAA,CAAQ,IAAI,CAAA,cAAA,CAAA;AAAA,UAC5B,EAAA,EAAI,OAAO,WAAW,CAAA,CAAA;AAAA,UAErB,QAAA,EAAA;AAAA;AAAA,OACH;AAAA,KAEJ;AAAA,IACA,EAAI,EAAA,cAAA;AAAA,IACJ,YAAY,CAAC,CAAA,EAAgB,CAC3B,KAAA,sBAAA,CAAuB,GAAG,CAAC;AAAA,GAC/B;AAAA,EACA;AAAA,IACE,KAAO,EAAA,MAAA;AAAA,IACP,KAAO,EAAA,MAAA;AAAA,IACP,IAAM,EAAA,SAAA;AAAA,IACN,YAAY,CAAC,CAAA,EAAgB,CAAmB,KAAA,CAAA,CAAE,UAAU,CAAE,CAAA;AAAA,GAChE;AAAA,EACA;AAAA,IACE,KAAO,EAAA,SAAA;AAAA,IACP,KAAO,EAAA,YAAA;AAAA,IACP,IAAM,EAAA,MAAA;AAAA,IACN,UAAA,kBAAa,GAAA,CAAA,GAAA,EAAA,EAAE,QAAK,EAAA,OAAA,EAAA;AAAA,GACtB;AAAA,EACA;AAAA,IACE,KAAO,EAAA,UAAA;AAAA,IACP,KAAO,EAAA,iBAAA;AAAA,IACP,IAAM,EAAA,QAAA;AAAA,IACN,UAAA,EAAY,CAAC,CAAgB,EAAA,CAAA,KAC3B,EAAE,mBAAoB,CAAA,aAAA,CAAc,EAAE,mBAAmB;AAAA;AAE/D;AAEa,MAAA,SAAA,GAAY,WAAW,CAAU,KAAA,MAAA;AAAA,EAC5C,KAAO,EAAA;AAAA,IACL,OAAA,EAAS,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACxB,OAAS,EAAA,MAAA;AAAA,IACT,cAAgB,EAAA;AAAA;AAEpB,CAAE,CAAA;;;;"}
1
+ {"version":3,"file":"tableHeading.esm.js","sources":["../../../src/components/QuayRepository/tableHeading.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ReactNode } from 'react';\n\nimport { Link, Progress, TableColumn } from '@backstage/core-components';\n\nimport { Tooltip } from '@material-ui/core';\nimport makeStyles from '@material-ui/core/styles/makeStyles';\n\nimport { securityScanComparator, vulnerabilitySummary } from '../../lib/utils';\nimport type { QuayTagData } from '../../types';\n\nexport const columns: TableColumn<QuayTagData>[] = [\n {\n title: 'Tag',\n field: 'name',\n type: 'string',\n highlight: true,\n },\n {\n title: 'Last Modified',\n field: 'last_modified',\n type: 'date',\n },\n {\n title: 'Security Scan',\n field: 'securityScan',\n render: (rowData: QuayTagData): ReactNode => {\n if (!rowData.securityStatus && !rowData.securityDetails) {\n return (\n <span data-testid=\"quay-repo-security-scan-progress\">\n <Progress />\n </span>\n );\n }\n\n if (rowData.securityStatus === 'queued') {\n return (\n <Tooltip title=\"The manifest for this tag is queued to be scanned for vulnerabilities\">\n <span data-testid=\"quay-repo-queued-for-scan\">Queued</span>\n </Tooltip>\n );\n }\n\n if (rowData.securityStatus === 'unsupported') {\n return (\n <Tooltip title=\"The manifest for this tag has an operating system or package manager unsupported by Quay Security Scanner\">\n <span data-testid=\"quay-repo-security-scan-unsupported\">\n Unsupported\n </span>\n </Tooltip>\n );\n }\n\n const tagManifest = rowData.manifest_digest_raw;\n const retStr = vulnerabilitySummary(rowData.securityDetails);\n return (\n <Link\n data-testid={`${rowData.name}-security-scan`}\n to={`tag/${tagManifest}`}\n >\n {retStr}\n </Link>\n );\n },\n id: 'securityScan',\n customSort: (a: QuayTagData, b: QuayTagData) =>\n securityScanComparator(a, b),\n },\n {\n title: 'Size',\n field: 'size',\n type: 'numeric',\n customSort: (a: QuayTagData, b: QuayTagData) => a.rawSize - b.rawSize,\n },\n {\n title: 'Expires',\n field: 'expiration',\n type: 'date',\n emptyValue: <i>Never</i>,\n },\n {\n title: 'Manifest',\n field: 'manifest_digest',\n type: 'string',\n customSort: (a: QuayTagData, b: QuayTagData) =>\n a.manifest_digest_raw.localeCompare(b.manifest_digest_raw),\n },\n];\n\nexport const useStyles = makeStyles(theme => ({\n empty: {\n padding: theme.spacing(2),\n display: 'flex',\n justifyContent: 'center',\n },\n}));\n"],"names":[],"mappings":";;;;;;AAyBO,MAAM,OAAsC,GAAA;AAAA,EACjD;AAAA,IACE,KAAO,EAAA,KAAA;AAAA,IACP,KAAO,EAAA,MAAA;AAAA,IACP,IAAM,EAAA,QAAA;AAAA,IACN,SAAW,EAAA;AAAA,GACb;AAAA,EACA;AAAA,IACE,KAAO,EAAA,eAAA;AAAA,IACP,KAAO,EAAA,eAAA;AAAA,IACP,IAAM,EAAA;AAAA,GACR;AAAA,EACA;AAAA,IACE,KAAO,EAAA,eAAA;AAAA,IACP,KAAO,EAAA,cAAA;AAAA,IACP,MAAA,EAAQ,CAAC,OAAoC,KAAA;AAC3C,MAAA,IAAI,CAAC,OAAA,CAAQ,cAAkB,IAAA,CAAC,QAAQ,eAAiB,EAAA;AACvD,QAAA,2BACG,MAAK,EAAA,EAAA,aAAA,EAAY,kCAChB,EAAA,QAAA,kBAAA,GAAA,CAAC,YAAS,CACZ,EAAA,CAAA;AAAA;AAIJ,MAAI,IAAA,OAAA,CAAQ,mBAAmB,QAAU,EAAA;AACvC,QACE,uBAAA,GAAA,CAAC,WAAQ,KAAM,EAAA,uEAAA,EACb,8BAAC,MAAK,EAAA,EAAA,aAAA,EAAY,2BAA4B,EAAA,QAAA,EAAA,QAAA,EAAM,CACtD,EAAA,CAAA;AAAA;AAIJ,MAAI,IAAA,OAAA,CAAQ,mBAAmB,aAAe,EAAA;AAC5C,QACE,uBAAA,GAAA,CAAC,WAAQ,KAAM,EAAA,2GAAA,EACb,8BAAC,MAAK,EAAA,EAAA,aAAA,EAAY,qCAAsC,EAAA,QAAA,EAAA,aAAA,EAExD,CACF,EAAA,CAAA;AAAA;AAIJ,MAAA,MAAM,cAAc,OAAQ,CAAA,mBAAA;AAC5B,MAAM,MAAA,MAAA,GAAS,oBAAqB,CAAA,OAAA,CAAQ,eAAe,CAAA;AAC3D,MACE,uBAAA,GAAA;AAAA,QAAC,IAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAa,CAAG,EAAA,OAAA,CAAQ,IAAI,CAAA,cAAA,CAAA;AAAA,UAC5B,EAAA,EAAI,OAAO,WAAW,CAAA,CAAA;AAAA,UAErB,QAAA,EAAA;AAAA;AAAA,OACH;AAAA,KAEJ;AAAA,IACA,EAAI,EAAA,cAAA;AAAA,IACJ,YAAY,CAAC,CAAA,EAAgB,CAC3B,KAAA,sBAAA,CAAuB,GAAG,CAAC;AAAA,GAC/B;AAAA,EACA;AAAA,IACE,KAAO,EAAA,MAAA;AAAA,IACP,KAAO,EAAA,MAAA;AAAA,IACP,IAAM,EAAA,SAAA;AAAA,IACN,YAAY,CAAC,CAAA,EAAgB,CAAmB,KAAA,CAAA,CAAE,UAAU,CAAE,CAAA;AAAA,GAChE;AAAA,EACA;AAAA,IACE,KAAO,EAAA,SAAA;AAAA,IACP,KAAO,EAAA,YAAA;AAAA,IACP,IAAM,EAAA,MAAA;AAAA,IACN,UAAA,kBAAa,GAAA,CAAA,GAAA,EAAA,EAAE,QAAK,EAAA,OAAA,EAAA;AAAA,GACtB;AAAA,EACA;AAAA,IACE,KAAO,EAAA,UAAA;AAAA,IACP,KAAO,EAAA,iBAAA;AAAA,IACP,IAAM,EAAA,QAAA;AAAA,IACN,UAAA,EAAY,CAAC,CAAgB,EAAA,CAAA,KAC3B,EAAE,mBAAoB,CAAA,aAAA,CAAc,EAAE,mBAAmB;AAAA;AAE/D;AAEyB,WAAW,CAAU,KAAA,MAAA;AAAA,EAC5C,KAAO,EAAA;AAAA,IACL,OAAA,EAAS,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACxB,OAAS,EAAA,MAAA;AAAA,IACT,cAAgB,EAAA;AAAA;AAEpB,CAAE,CAAA;;;;"}
@@ -0,0 +1,7 @@
1
+ const DOC_LINKS = {
2
+ AUTH_TOKEN_GUIDE: "https://docs.redhat.com/en/documentation/red_hat_quay/3/html-single/red_hat_quay_api_guide/index#creating-oauth-access-token",
3
+ BACKEND_CONFIGURATION_GUIDE: "https://github.com/backstage/community-plugins/tree/main/workspaces/quay/plugins/quay-backend#app-config"
4
+ };
5
+
6
+ export { DOC_LINKS };
7
+ //# sourceMappingURL=doc-links.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-links.esm.js","sources":["../src/doc-links.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const DOC_LINKS = {\n AUTH_TOKEN_GUIDE:\n 'https://docs.redhat.com/en/documentation/red_hat_quay/3/html-single/red_hat_quay_api_guide/index#creating-oauth-access-token',\n BACKEND_CONFIGURATION_GUIDE:\n 'https://github.com/backstage/community-plugins/tree/main/workspaces/quay/plugins/quay-backend#app-config',\n} as const;\n"],"names":[],"mappings":"AAgBO,MAAM,SAAY,GAAA;AAAA,EACvB,gBACE,EAAA,8HAAA;AAAA,EACF,2BACE,EAAA;AACJ;;;;"}
@@ -3,10 +3,9 @@ import { useState, useMemo } from 'react';
3
3
  import { useAsync } from 'react-use';
4
4
  import { useApi } from '@backstage/core-plugin-api';
5
5
  import { useEntity } from '@backstage/plugin-catalog-react';
6
- import { formatDate } from '@janus-idp/shared-react';
7
6
  import { makeStyles, Box, Chip } from '@material-ui/core';
8
7
  import { quayApiRef } from '../api/index.esm.js';
9
- import { formatByteSize } from '../utils.esm.js';
8
+ import { formatByteSize, formatDate } from '../utils.esm.js';
10
9
 
11
10
  const useLocalStyles = makeStyles({
12
11
  chip: {
@@ -1 +1 @@
1
- {"version":3,"file":"quay.esm.js","sources":["../../src/hooks/quay.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useMemo, useState } from 'react';\nimport { useAsync } from 'react-use';\n\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\n\nimport { formatDate } from '@janus-idp/shared-react';\nimport { Box, Chip, makeStyles } from '@material-ui/core';\n\nimport { quayApiRef } from '../api';\nimport { Layer, QuayTagData, Tag } from '../types';\nimport { formatByteSize } from '../utils';\n\nconst useLocalStyles = makeStyles({\n chip: {\n margin: 0,\n marginRight: '.2em',\n height: '1.5em',\n '& > span': {\n padding: '.3em',\n },\n },\n});\n\nexport const useTags = (organization: string, repository: string) => {\n const quayClient = useApi(quayApiRef);\n const [tags, setTags] = useState<Tag[]>([]);\n const [tagManifestLayers, setTagManifestLayers] = useState<\n Record<string, Layer>\n >({});\n const [tagManifestStatuses, setTagManifestStatuses] = useState<\n Record<string, string>\n >({});\n const localClasses = useLocalStyles();\n\n const fetchSecurityDetails = async (tag: Tag) => {\n const securityDetails = await quayClient.getSecurityDetails(\n organization,\n repository,\n tag.manifest_digest,\n );\n return securityDetails;\n };\n\n const { loading } = useAsync(async () => {\n const tagsResponse = await quayClient.getTags(organization, repository);\n Promise.all(\n tagsResponse.tags.map(async tag => {\n const securityDetails = await fetchSecurityDetails(tag);\n const securityData = securityDetails.data;\n const securityStatus = securityDetails.status;\n\n setTagManifestStatuses(prevState => ({\n ...prevState,\n [tag.manifest_digest]: securityStatus,\n }));\n\n if (securityData) {\n setTagManifestLayers(prevState => ({\n ...prevState,\n [tag.manifest_digest]: securityData.Layer,\n }));\n }\n }),\n );\n setTags(prevTags => [...prevTags, ...tagsResponse.tags]);\n return tagsResponse;\n });\n\n const data: QuayTagData[] = useMemo(() => {\n return Object.values(tags)?.map(tag => {\n const hashFunc = tag.manifest_digest.substring(0, 6);\n const shortHash = tag.manifest_digest.substring(7, 19);\n return {\n id: `${tag.manifest_digest}-${tag.name}`,\n name: tag.name,\n last_modified: formatDate(tag.last_modified),\n size: formatByteSize(tag.size),\n rawSize: tag.size,\n manifest_digest: (\n <Box sx={{ display: 'flex', alignItems: 'center' }}>\n <Chip label={hashFunc} className={localClasses.chip} />\n {shortHash}\n </Box>\n ),\n expiration: tag.expiration,\n securityDetails: tagManifestLayers[tag.manifest_digest],\n securityStatus: tagManifestStatuses[tag.manifest_digest],\n manifest_digest_raw: tag.manifest_digest,\n // is_manifest_list: tag.is_manifest_list,\n // reversion: tag.reversion,\n // start_ts: tag.start_ts,\n // end_ts: tag.end_ts,\n // manifest_list: tag.manifest_list,\n };\n });\n }, [tags, localClasses.chip, tagManifestLayers, tagManifestStatuses]);\n\n return { loading, data };\n};\n\nexport const QUAY_ANNOTATION_REPOSITORY = 'quay.io/repository-slug';\n\nexport const useQuayAppData = ({ entity }: { entity: Entity }) => {\n const repositorySlug =\n entity?.metadata.annotations?.[QUAY_ANNOTATION_REPOSITORY] ?? '';\n\n if (!repositorySlug) {\n throw new Error(\"'Quay' annotations are missing\");\n }\n return { repositorySlug };\n};\n\nexport const useRepository = () => {\n const { entity } = useEntity();\n const { repositorySlug } = useQuayAppData({ entity });\n const info = repositorySlug.split('/');\n\n const organization = info.shift() as 'string';\n const repository = info.join('/');\n return {\n organization,\n repository,\n };\n};\n\nexport const useTagDetails = (org: string, repo: string, digest: string) => {\n const quayClient = useApi(quayApiRef);\n const result = useAsync(async () => {\n const manifestLayer = await quayClient.getSecurityDetails(\n org,\n repo,\n digest,\n );\n return manifestLayer;\n });\n return result;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"quay.esm.js","sources":["../../src/hooks/quay.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useMemo, useState } from 'react';\nimport { useAsync } from 'react-use';\n\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\n\nimport { Box, Chip, makeStyles } from '@material-ui/core';\n\nimport { quayApiRef } from '../api';\nimport { Layer, QuayTagData, Tag } from '../types';\nimport { formatByteSize, formatDate } from '../utils';\n\nconst useLocalStyles = makeStyles({\n chip: {\n margin: 0,\n marginRight: '.2em',\n height: '1.5em',\n '& > span': {\n padding: '.3em',\n },\n },\n});\n\nexport const useTags = (organization: string, repository: string) => {\n const quayClient = useApi(quayApiRef);\n const [tags, setTags] = useState<Tag[]>([]);\n const [tagManifestLayers, setTagManifestLayers] = useState<\n Record<string, Layer>\n >({});\n const [tagManifestStatuses, setTagManifestStatuses] = useState<\n Record<string, string>\n >({});\n const localClasses = useLocalStyles();\n\n const fetchSecurityDetails = async (tag: Tag) => {\n const securityDetails = await quayClient.getSecurityDetails(\n organization,\n repository,\n tag.manifest_digest,\n );\n return securityDetails;\n };\n\n const { loading } = useAsync(async () => {\n const tagsResponse = await quayClient.getTags(organization, repository);\n Promise.all(\n tagsResponse.tags.map(async tag => {\n const securityDetails = await fetchSecurityDetails(tag);\n const securityData = securityDetails.data;\n const securityStatus = securityDetails.status;\n\n setTagManifestStatuses(prevState => ({\n ...prevState,\n [tag.manifest_digest]: securityStatus,\n }));\n\n if (securityData) {\n setTagManifestLayers(prevState => ({\n ...prevState,\n [tag.manifest_digest]: securityData.Layer,\n }));\n }\n }),\n );\n setTags(prevTags => [...prevTags, ...tagsResponse.tags]);\n return tagsResponse;\n });\n\n const data: QuayTagData[] = useMemo(() => {\n return Object.values(tags)?.map(tag => {\n const hashFunc = tag.manifest_digest.substring(0, 6);\n const shortHash = tag.manifest_digest.substring(7, 19);\n return {\n id: `${tag.manifest_digest}-${tag.name}`,\n name: tag.name,\n last_modified: formatDate(tag.last_modified),\n size: formatByteSize(tag.size),\n rawSize: tag.size,\n manifest_digest: (\n <Box sx={{ display: 'flex', alignItems: 'center' }}>\n <Chip label={hashFunc} className={localClasses.chip} />\n {shortHash}\n </Box>\n ),\n expiration: tag.expiration,\n securityDetails: tagManifestLayers[tag.manifest_digest],\n securityStatus: tagManifestStatuses[tag.manifest_digest],\n manifest_digest_raw: tag.manifest_digest,\n // is_manifest_list: tag.is_manifest_list,\n // reversion: tag.reversion,\n // start_ts: tag.start_ts,\n // end_ts: tag.end_ts,\n // manifest_list: tag.manifest_list,\n };\n });\n }, [tags, localClasses.chip, tagManifestLayers, tagManifestStatuses]);\n\n return { loading, data };\n};\n\nexport const QUAY_ANNOTATION_REPOSITORY = 'quay.io/repository-slug';\n\nexport const useQuayAppData = ({ entity }: { entity: Entity }) => {\n const repositorySlug =\n entity?.metadata.annotations?.[QUAY_ANNOTATION_REPOSITORY] ?? '';\n\n if (!repositorySlug) {\n throw new Error(\"'Quay' annotations are missing\");\n }\n return { repositorySlug };\n};\n\nexport const useRepository = () => {\n const { entity } = useEntity();\n const { repositorySlug } = useQuayAppData({ entity });\n const info = repositorySlug.split('/');\n\n const organization = info.shift() as 'string';\n const repository = info.join('/');\n return {\n organization,\n repository,\n };\n};\n\nexport const useTagDetails = (org: string, repo: string, digest: string) => {\n const quayClient = useApi(quayApiRef);\n const result = useAsync(async () => {\n const manifestLayer = await quayClient.getSecurityDetails(\n org,\n repo,\n digest,\n );\n return manifestLayer;\n });\n return result;\n};\n"],"names":[],"mappings":"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"}
package/dist/utils.esm.js CHANGED
@@ -1,9 +1,29 @@
1
1
  import { filesize } from 'filesize';
2
+ import { DateTime } from 'luxon';
2
3
 
3
4
  function formatByteSize(sizeInBytes) {
4
5
  if (!sizeInBytes) return "N/A";
5
6
  return filesize(sizeInBytes);
6
7
  }
8
+ function formatDate(date) {
9
+ if (!date || date === -1) {
10
+ return "N/A";
11
+ }
12
+ let dt;
13
+ if (typeof date === "number") {
14
+ dt = DateTime.fromSeconds(date);
15
+ } else if (date instanceof Date) {
16
+ dt = DateTime.fromJSDate(date);
17
+ } else if (/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),/.test(date)) {
18
+ dt = DateTime.fromRFC2822(date);
19
+ } else {
20
+ dt = DateTime.fromISO(date);
21
+ }
22
+ if (!dt.isValid) {
23
+ return "N/A";
24
+ }
25
+ return dt.toLocaleString(DateTime.DATETIME_MED);
26
+ }
7
27
 
8
- export { formatByteSize };
28
+ export { formatByteSize, formatDate };
9
29
  //# sourceMappingURL=utils.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.esm.js","sources":["../src/utils.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { filesize } from 'filesize';\n\nexport function formatByteSize(sizeInBytes: number | undefined): string {\n if (!sizeInBytes) return 'N/A';\n\n return filesize(sizeInBytes);\n}\n"],"names":[],"mappings":";;AAkBO,SAAS,eAAe,WAAyC,EAAA;AACtE,EAAI,IAAA,CAAC,aAAoB,OAAA,KAAA;AAEzB,EAAA,OAAO,SAAS,WAAW,CAAA;AAC7B;;;;"}
1
+ {"version":3,"file":"utils.esm.js","sources":["../src/utils.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { filesize } from 'filesize';\nimport { DateTime } from 'luxon';\n\nexport function formatByteSize(sizeInBytes: number | undefined): string {\n if (!sizeInBytes) return 'N/A';\n\n return filesize(sizeInBytes);\n}\n\n/**\n * Formats a date using the Backstage-standard Luxon library.\n *\n * @param date - The date to format, can be a string, number (Unix timestamp), or Date object.\n * @returns A formatted date string (e.g., \"Jun 9, 2025, 6:15 PM\") or 'N/A'.\n */\nexport function formatDate(date: string | number | Date | undefined): string {\n if (!date || date === -1) {\n return 'N/A';\n }\n\n let dt: DateTime;\n\n if (typeof date === 'number') {\n dt = DateTime.fromSeconds(date);\n } else if (date instanceof Date) {\n dt = DateTime.fromJSDate(date);\n } else if (/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),/.test(date)) {\n // Check if it's RFC 2822 format (starts with day name)\n dt = DateTime.fromRFC2822(date);\n } else {\n dt = DateTime.fromISO(date);\n }\n\n if (!dt.isValid) {\n return 'N/A';\n }\n return dt.toLocaleString(DateTime.DATETIME_MED);\n}\n"],"names":[],"mappings":";;;AAmBO,SAAS,eAAe,WAAyC,EAAA;AACtE,EAAI,IAAA,CAAC,aAAoB,OAAA,KAAA;AAEzB,EAAA,OAAO,SAAS,WAAW,CAAA;AAC7B;AAQO,SAAS,WAAW,IAAkD,EAAA;AAC3E,EAAI,IAAA,CAAC,IAAQ,IAAA,IAAA,KAAS,EAAI,EAAA;AACxB,IAAO,OAAA,KAAA;AAAA;AAGT,EAAI,IAAA,EAAA;AAEJ,EAAI,IAAA,OAAO,SAAS,QAAU,EAAA;AAC5B,IAAK,EAAA,GAAA,QAAA,CAAS,YAAY,IAAI,CAAA;AAAA,GAChC,MAAA,IAAW,gBAAgB,IAAM,EAAA;AAC/B,IAAK,EAAA,GAAA,QAAA,CAAS,WAAW,IAAI,CAAA;AAAA,GACpB,MAAA,IAAA,iCAAA,CAAkC,IAAK,CAAA,IAAI,CAAG,EAAA;AAEvD,IAAK,EAAA,GAAA,QAAA,CAAS,YAAY,IAAI,CAAA;AAAA,GACzB,MAAA;AACL,IAAK,EAAA,GAAA,QAAA,CAAS,QAAQ,IAAI,CAAA;AAAA;AAG5B,EAAI,IAAA,CAAC,GAAG,OAAS,EAAA;AACf,IAAO,OAAA,KAAA;AAAA;AAET,EAAO,OAAA,EAAA,CAAG,cAAe,CAAA,QAAA,CAAS,YAAY,CAAA;AAChD;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-quay",
3
- "version": "1.21.1",
3
+ "version": "1.22.1",
4
4
  "main": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -44,11 +44,11 @@
44
44
  "@backstage/plugin-catalog-react": "^1.18.0",
45
45
  "@backstage/plugin-permission-react": "^0.4.34",
46
46
  "@backstage/theme": "^0.6.6",
47
- "@janus-idp/shared-react": "^2.16.0",
48
47
  "@material-ui/core": "^4.12.2",
49
48
  "@material-ui/icons": "^4.11.3",
50
49
  "@material-ui/lab": "4.0.0-alpha.61",
51
50
  "filesize": "^10.1.6",
51
+ "luxon": "^3.6.1",
52
52
  "react-use": "^17.4.0"
53
53
  },
54
54
  "peerDependencies": {