@backstage-community/plugin-quay 1.26.0 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  ### Dependencies
2
2
 
3
+ ## 1.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 482a213: Backstage version bump to v1.45.3
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [482a213]
12
+ - @backstage-community/plugin-quay-common@1.15.0
13
+
14
+ ## 1.27.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 40d312e: **BREAKING**: Added support for multiple Quay instances. Backend plugin routes now require an `:instance-name` parameter to support multiple Quay instances:
19
+
20
+ - `/repository/:org/:repo/tag` → `/:instanceName/repository/:org/:repo/tag`
21
+ - `/repository/:org/:repo/manifest/{digest}` → `/:instanceName/repository/:org/:repo/manifest/{digest}`
22
+ - `/repository/:org/:repo/manifest/{digest}/labels` → `/:instanceName/repository/:org/:repo/manifest/{digest}/labels`
23
+ - `/repository/:org/:repo/manifest/{digest}/security` → `/instanceName/repository/:org/:repo/manifest/{digest}/security`
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [40d312e]
28
+ - @backstage-community/plugin-quay-common@1.14.0
29
+
3
30
  ## 1.26.0
4
31
 
5
32
  ### Minor Changes
package/README.md CHANGED
@@ -72,9 +72,64 @@ If you require more functionality like being able to set permissions, please see
72
72
  ```yaml title="catalog-info.yaml"
73
73
  metadata:
74
74
  annotations:
75
- 'quay.io/repository-slug': `<ORGANIZATION>/<REPOSITORY>',
75
+ 'quay.io/repository-slug': '<ORGANIZATION>/<REPOSITORY>'
76
76
  ```
77
77
 
78
+ #### Multiple Quay Instances Configuration
79
+
80
+ You can connect to multiple Quay instances by following configuration:
81
+
82
+ ```yaml title="app-config.yaml"
83
+ proxy:
84
+ endpoints:
85
+ '/quay/api':
86
+ target: 'https://quay.io'
87
+ credentials: require
88
+ headers:
89
+ X-Requested-With: 'XMLHttpRequest'
90
+ # Uncomment and use the Authorization header below to access a private Quay
91
+ # Repository using a token. Refer to the "Applications and Tokens" section
92
+ # at https://docs.quay.io/api/ to find the instructions to generate a token
93
+ # Authorization: 'Bearer <YOUR TOKEN>'
94
+ changeOrigin: true
95
+ # Change to "false" in case of using self hosted quay instance with a self-signed certificate
96
+ secure: true
97
+ '/quay-staging/api':
98
+ target: 'https://quay-staging.example.com'
99
+ credentials: require
100
+ headers:
101
+ X-Requested-With: 'XMLHttpRequest'
102
+ # Uncomment and use the Authorization header below to access a private Quay
103
+ # Repository using a token. Refer to the "Applications and Tokens" section
104
+ # at https://docs.quay.io/api/ to find the instructions to generate a token
105
+ # Authorization: 'Bearer <YOUR TOKEN>'
106
+ changeOrigin: true
107
+ # Change to "false" in case of using self hosted quay instance with a self-signed certificate
108
+ secure: true
109
+
110
+ quay:
111
+ instances:
112
+ - name: production
113
+ proxyPath: '/quay/api'
114
+ uiUrl: 'https://quay.io'
115
+ - name: staging
116
+ proxyPath: '/quay-staging/api'
117
+ uiUrl: 'https://quay-staging.example.com'
118
+ ```
119
+
120
+ When using multiple instances, specify the target instance in your entity using the `quay.io/instance-name` annotation, the instance name must match a name defined in your instances configuration:
121
+
122
+ ```yaml title="catalog-info.yaml"
123
+ metadata:
124
+ annotations:
125
+ 'quay.io/repository-slug': '<ORGANIZATION>/<REPOSITORY>'
126
+ 'quay.io/instance-name': 'production'
127
+ ```
128
+
129
+ **Note:** If the `quay.io/instance-name` annotation is not specified, the plugin will automatically use the first configured instance as the default.
130
+
131
+ When using the Quay backend plugin, refer to the [Quay backend plugin documentation](../quay-backend/README.md#multiple-quay-instances-configuration) for multi-instance configuration.
132
+
78
133
  ## For users
79
134
 
80
135
  ### Using the Quay plugin in Backstage
package/config.d.ts CHANGED
@@ -15,24 +15,57 @@
15
15
  */
16
16
  export interface Config {
17
17
  /** Configurations for the Quay plugin */
18
- quay?: {
19
- /**
20
- * The proxy path for the Quay instance.
21
- * @visibility frontend
22
- */
23
- proxyPath?: string;
24
- /**
25
- * The UI url of the Quay instance.
26
- * @visibility frontend
27
- */
28
- uiUrl?: string;
29
- /**
30
- * The API URl for a quay instance.
31
- * This is set for the quay-backend plugin.
32
- * If this is set, we use the quay-backend plugin.
33
- * If not, we default to using the proxy config.
34
- * @visibility frontend
35
- */
36
- apiUrl?: string;
37
- };
18
+ quay:
19
+ | {
20
+ /**
21
+ * Multiple Quay instances configuration.
22
+ * @visibility frontend
23
+ */
24
+ instances: Array<{
25
+ /**
26
+ * The name identifier for this Quay instance.
27
+ * @visibility frontend
28
+ */
29
+ name: string;
30
+ /**
31
+ * The UI url of the Quay instance.
32
+ * @visibility frontend
33
+ */
34
+ uiUrl?: string;
35
+ /**
36
+ * The proxy path for this Quay instance.
37
+ * Only used if apiUrl is not set.
38
+ * @visibility frontend
39
+ */
40
+ proxyPath?: string;
41
+ /**
42
+ * The API URL for a quay instance.
43
+ * This is set for the quay-backend plugin.
44
+ * If this is set, we use the quay-backend plugin.
45
+ * If not, we default to using the proxy config.
46
+ * @visibility frontend
47
+ */
48
+ apiUrl?: string;
49
+ }>;
50
+ }
51
+ | {
52
+ /**
53
+ * The proxy path for the Quay instance.
54
+ * @visibility frontend
55
+ */
56
+ proxyPath?: string;
57
+ /**
58
+ * The UI url of the Quay instance.
59
+ * @visibility frontend
60
+ */
61
+ uiUrl?: string;
62
+ /**
63
+ * The API URL for a quay instance.
64
+ * This is set for the quay-backend plugin.
65
+ * If this is set, we use the quay-backend plugin.
66
+ * If not, we default to using the proxy config.
67
+ * @visibility frontend
68
+ */
69
+ apiUrl?: string;
70
+ };
38
71
  }
@@ -1,4 +1,5 @@
1
1
  import { createApiRef } from '@backstage/core-plugin-api';
2
+ import { QUAY_SINGLE_INSTANCE_NAME } from '@backstage-community/plugin-quay-common';
2
3
 
3
4
  const DEFAULT_PROXY_PATH = "/quay/api";
4
5
  const quayApiRef = createApiRef({
@@ -7,20 +8,73 @@ const quayApiRef = createApiRef({
7
8
  class QuayApiClient {
8
9
  // @ts-ignore
9
10
  discoveryApi;
10
- configApi;
11
11
  identityApi;
12
- constructor(options) {
13
- this.discoveryApi = options.discoveryApi;
14
- this.configApi = options.configApi;
15
- this.identityApi = options.identityApi;
12
+ instances;
13
+ defaultInstanceName;
14
+ static fromConfig(options) {
15
+ const { configApi } = options;
16
+ const instancesArray = configApi.getOptionalConfigArray("quay.instances");
17
+ const singleApiUrl = configApi.getOptionalString("quay.apiUrl");
18
+ const singleProxyPath = configApi.getOptionalString("quay.proxyPath");
19
+ const singleUiUrl = configApi.getOptionalString("quay.uiUrl");
20
+ if (instancesArray && (singleApiUrl || singleProxyPath || singleUiUrl)) {
21
+ throw new Error(
22
+ 'Invalid Quay configuration: Cannot use both "quay.instances" (multi-instance) and "quay.apiUrl", "quay.proxyPath", "quay.uiUrl" (single-instance) at the same time.'
23
+ );
24
+ }
25
+ const instances = [];
26
+ if (instancesArray && instancesArray.length > 0) {
27
+ for (const instance of instancesArray) {
28
+ instances.push({
29
+ name: instance.getString("name"),
30
+ uiUrl: instance.getOptionalString("uiUrl"),
31
+ apiUrl: instance.getOptionalString("apiUrl"),
32
+ proxyPath: instance.getOptionalString("proxyPath")
33
+ });
34
+ }
35
+ } else if (singleApiUrl) {
36
+ instances.push({
37
+ name: QUAY_SINGLE_INSTANCE_NAME,
38
+ apiUrl: singleApiUrl
39
+ });
40
+ } else {
41
+ instances.push({
42
+ name: QUAY_SINGLE_INSTANCE_NAME,
43
+ uiUrl: singleUiUrl,
44
+ proxyPath: singleProxyPath
45
+ });
46
+ }
47
+ return new QuayApiClient(
48
+ options.discoveryApi,
49
+ options.identityApi,
50
+ instances
51
+ );
16
52
  }
17
- async getBaseUrl() {
18
- const apiUrl = this.configApi.getOptionalString("quay.apiUrl");
19
- const proxyPath = this.configApi.getOptionalString("quay.proxyPath") ?? DEFAULT_PROXY_PATH;
53
+ constructor(discoveryApi, identityApi, instances) {
54
+ if (instances.length === 0) {
55
+ throw new Error("At least one Quay instance must be configured");
56
+ }
57
+ this.discoveryApi = discoveryApi;
58
+ this.identityApi = identityApi;
59
+ this.instances = new Map(
60
+ instances.map((instance) => [instance.name, instance])
61
+ );
62
+ this.defaultInstanceName = instances[0].name;
63
+ }
64
+ getQuayInstance(instanceName) {
65
+ return instanceName ? this.instances.get(instanceName) : this.instances.get(this.defaultInstanceName);
66
+ }
67
+ async getBaseUrl(instanceName) {
68
+ const instance = this.getQuayInstance(instanceName);
69
+ if (instance === undefined) {
70
+ throw new Error(
71
+ `Quay instance "${instanceName}" not found in configuration.`
72
+ );
73
+ }
20
74
  const baseUrl = await this.discoveryApi.getBaseUrl(
21
- apiUrl ? "quay" : "proxy"
75
+ instance.apiUrl ? "quay" : "proxy"
22
76
  );
23
- return apiUrl ? baseUrl : `${baseUrl}${proxyPath}/api/v1`;
77
+ return instance.apiUrl ? `${baseUrl}/${instance.name}` : `${baseUrl}${instance.proxyPath ?? DEFAULT_PROXY_PATH}/api/v1`;
24
78
  }
25
79
  async fetcher(url) {
26
80
  const { token: idToken } = await this.identityApi.getCredentials();
@@ -42,8 +96,8 @@ class QuayApiClient {
42
96
  (k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`
43
97
  ).join("&");
44
98
  }
45
- async getTags(org, repo, page, limit, specificTag) {
46
- const baseUrl = await this.getBaseUrl();
99
+ async getTags(instanceName, org, repo, page, limit, specificTag) {
100
+ const baseUrl = await this.getBaseUrl(instanceName);
47
101
  const params = this.encodeGetParams({
48
102
  limit,
49
103
  page,
@@ -54,20 +108,20 @@ class QuayApiClient {
54
108
  `${baseUrl}/repository/${org}/${repo}/tag/?${params}`
55
109
  );
56
110
  }
57
- async getLabels(org, repo, digest) {
58
- const baseUrl = await this.getBaseUrl();
111
+ async getLabels(instanceName, org, repo, digest) {
112
+ const baseUrl = await this.getBaseUrl(instanceName);
59
113
  return await this.fetcher(
60
114
  `${baseUrl}/repository/${org}/${repo}/manifest/${digest}/labels`
61
115
  );
62
116
  }
63
- async getManifestByDigest(org, repo, digest) {
64
- const baseUrl = await this.getBaseUrl();
117
+ async getManifestByDigest(instanceName, org, repo, digest) {
118
+ const baseUrl = await this.getBaseUrl(instanceName);
65
119
  return await this.fetcher(
66
120
  `${baseUrl}/repository/${org}/${repo}/manifest/${digest}`
67
121
  );
68
122
  }
69
- async getSecurityDetails(org, repo, digest) {
70
- const baseUrl = await this.getBaseUrl();
123
+ async getSecurityDetails(instanceName, org, repo, digest) {
124
+ const baseUrl = await this.getBaseUrl(instanceName);
71
125
  return await this.fetcher(
72
126
  `${baseUrl}/repository/${org}/${repo}/manifest/${digest}/security`
73
127
  );
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../../src/api/index.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 */\nimport {\n ConfigApi,\n createApiRef,\n DiscoveryApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\n\nimport {\n LabelsResponse,\n ManifestByDigestResponse,\n SecurityDetailsResponse,\n TagsResponse,\n} from '../types';\n\nconst DEFAULT_PROXY_PATH = '/quay/api';\n\nexport interface QuayApiV1 {\n getTags(\n org: string,\n repo: string,\n page?: number,\n limit?: number,\n ): Promise<TagsResponse>;\n getLabels(org: string, repo: string, digest: string): Promise<LabelsResponse>;\n getManifestByDigest(\n org: string,\n repo: string,\n digest: string,\n ): Promise<ManifestByDigestResponse>;\n getSecurityDetails(\n org: string,\n repo: string,\n digest: string,\n ): Promise<SecurityDetailsResponse>;\n}\n\nexport const quayApiRef = createApiRef<QuayApiV1>({\n id: 'plugin.quay.service',\n});\n\nexport type Options = {\n discoveryApi: DiscoveryApi;\n configApi: ConfigApi;\n identityApi: IdentityApi;\n};\n\nexport class QuayApiClient implements QuayApiV1 {\n // @ts-ignore\n private readonly discoveryApi: DiscoveryApi;\n\n private readonly configApi: ConfigApi;\n\n private readonly identityApi: IdentityApi;\n\n constructor(options: Options) {\n this.discoveryApi = options.discoveryApi;\n this.configApi = options.configApi;\n this.identityApi = options.identityApi;\n }\n\n private async getBaseUrl() {\n // Check if the user opted into the quay-backend\n // Default to proxy if not\n const apiUrl = this.configApi.getOptionalString('quay.apiUrl');\n const proxyPath =\n this.configApi.getOptionalString('quay.proxyPath') ?? DEFAULT_PROXY_PATH;\n const baseUrl = await this.discoveryApi.getBaseUrl(\n apiUrl ? 'quay' : 'proxy',\n );\n\n return apiUrl ? baseUrl : `${baseUrl}${proxyPath}/api/v1`;\n }\n\n private async fetcher(url: string) {\n const { token: idToken } = await this.identityApi.getCredentials();\n const response = await fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n ...(idToken && { Authorization: `Bearer ${idToken}` }),\n },\n });\n if (!response.ok) {\n throw new Error(\n `failed to fetch data, status ${response.status}: ${response.statusText}`,\n );\n }\n return await response.json();\n }\n\n private encodeGetParams(params: Record<string, any>) {\n return Object.keys(params)\n .filter(key => typeof params[key] !== 'undefined')\n .map(\n k =>\n `${encodeURIComponent(k)}=${encodeURIComponent(params[k] as string)}`,\n )\n .join('&');\n }\n\n async getTags(\n org: string,\n repo: string,\n page?: number,\n limit?: number,\n specificTag?: string,\n ) {\n const baseUrl = await this.getBaseUrl();\n const params = this.encodeGetParams({\n limit,\n page,\n onlyActiveTags: true,\n specificTag,\n });\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/tag/?${params}`,\n )) as TagsResponse;\n }\n\n async getLabels(org: string, repo: string, digest: string) {\n const baseUrl = await this.getBaseUrl();\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/manifest/${digest}/labels`,\n )) as LabelsResponse;\n }\n\n async getManifestByDigest(org: string, repo: string, digest: string) {\n const baseUrl = await this.getBaseUrl();\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/manifest/${digest}`,\n )) as ManifestByDigestResponse;\n }\n\n async getSecurityDetails(org: string, repo: string, digest: string) {\n const baseUrl = await this.getBaseUrl();\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/manifest/${digest}/security`,\n )) as SecurityDetailsResponse;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../../src/api/index.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 */\nimport {\n ConfigApi,\n createApiRef,\n DiscoveryApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\n\nimport { QUAY_SINGLE_INSTANCE_NAME } from '@backstage-community/plugin-quay-common';\n\nimport {\n LabelsResponse,\n ManifestByDigestResponse,\n SecurityDetailsResponse,\n TagsResponse,\n} from '../types';\n\nconst DEFAULT_PROXY_PATH = '/quay/api';\n\nexport interface QuayApiV1 {\n getQuayInstance(instanceName?: string): QuayInstanceConfig | undefined;\n getTags(\n instanceName: string | undefined,\n org: string,\n repo: string,\n page?: number,\n limit?: number,\n ): Promise<TagsResponse>;\n getLabels(\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n ): Promise<LabelsResponse>;\n getManifestByDigest(\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n ): Promise<ManifestByDigestResponse>;\n getSecurityDetails(\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n ): Promise<SecurityDetailsResponse>;\n}\n\nexport const quayApiRef = createApiRef<QuayApiV1>({\n id: 'plugin.quay.service',\n});\n\nexport type Options = {\n discoveryApi: DiscoveryApi;\n configApi: ConfigApi;\n identityApi: IdentityApi;\n};\n\nexport type QuayInstanceConfig = {\n name: string;\n apiUrl?: string;\n uiUrl?: string;\n proxyPath?: string;\n};\n\nexport class QuayApiClient implements QuayApiV1 {\n // @ts-ignore\n private readonly discoveryApi: DiscoveryApi;\n\n private readonly identityApi: IdentityApi;\n\n private readonly instances: Map<string, QuayInstanceConfig>;\n\n private readonly defaultInstanceName: string;\n\n static fromConfig(options: Options): QuayApiClient {\n const { configApi } = options;\n\n const instancesArray = configApi.getOptionalConfigArray('quay.instances');\n const singleApiUrl = configApi.getOptionalString('quay.apiUrl');\n const singleProxyPath = configApi.getOptionalString('quay.proxyPath');\n const singleUiUrl = configApi.getOptionalString('quay.uiUrl');\n\n // Validate that single-instance and multi-instance configs are not mixed\n if (instancesArray && (singleApiUrl || singleProxyPath || singleUiUrl)) {\n throw new Error(\n 'Invalid Quay configuration: Cannot use both \"quay.instances\" (multi-instance) and \"quay.apiUrl\", \"quay.proxyPath\", \"quay.uiUrl\" (single-instance) at the same time.',\n );\n }\n\n const instances: QuayInstanceConfig[] = [];\n if (instancesArray && instancesArray.length > 0) {\n // Multi-instance configuration\n for (const instance of instancesArray) {\n instances.push({\n name: instance.getString('name'),\n uiUrl: instance.getOptionalString('uiUrl'),\n apiUrl: instance.getOptionalString('apiUrl'),\n proxyPath: instance.getOptionalString('proxyPath'),\n });\n }\n } else if (singleApiUrl) {\n // Single-instance configuration\n instances.push({\n name: QUAY_SINGLE_INSTANCE_NAME,\n apiUrl: singleApiUrl,\n });\n } else {\n // Single-instance proxy configuration\n instances.push({\n name: QUAY_SINGLE_INSTANCE_NAME,\n uiUrl: singleUiUrl,\n proxyPath: singleProxyPath,\n });\n }\n\n return new QuayApiClient(\n options.discoveryApi,\n options.identityApi,\n instances,\n );\n }\n\n private constructor(\n discoveryApi: DiscoveryApi,\n identityApi: IdentityApi,\n instances: QuayInstanceConfig[],\n ) {\n if (instances.length === 0) {\n throw new Error('At least one Quay instance must be configured');\n }\n\n this.discoveryApi = discoveryApi;\n this.identityApi = identityApi;\n this.instances = new Map(\n instances.map(instance => [instance.name, instance]),\n );\n this.defaultInstanceName = instances[0].name;\n }\n\n getQuayInstance(instanceName?: string): QuayInstanceConfig | undefined {\n return instanceName\n ? this.instances.get(instanceName)\n : this.instances.get(this.defaultInstanceName);\n }\n\n private async getBaseUrl(instanceName?: string) {\n const instance = this.getQuayInstance(instanceName);\n if (instance === undefined) {\n throw new Error(\n `Quay instance \"${instanceName}\" not found in configuration.`,\n );\n }\n\n const baseUrl = await this.discoveryApi.getBaseUrl(\n instance.apiUrl ? 'quay' : 'proxy',\n );\n return instance.apiUrl\n ? `${baseUrl}/${instance.name}`\n : `${baseUrl}${instance.proxyPath ?? DEFAULT_PROXY_PATH}/api/v1`;\n }\n\n private async fetcher(url: string) {\n const { token: idToken } = await this.identityApi.getCredentials();\n const response = await fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n ...(idToken && { Authorization: `Bearer ${idToken}` }),\n },\n });\n if (!response.ok) {\n throw new Error(\n `failed to fetch data, status ${response.status}: ${response.statusText}`,\n );\n }\n return await response.json();\n }\n\n private encodeGetParams(params: Record<string, any>) {\n return Object.keys(params)\n .filter(key => typeof params[key] !== 'undefined')\n .map(\n k =>\n `${encodeURIComponent(k)}=${encodeURIComponent(params[k] as string)}`,\n )\n .join('&');\n }\n\n async getTags(\n instanceName: string | undefined,\n org: string,\n repo: string,\n page?: number,\n limit?: number,\n specificTag?: string,\n ) {\n const baseUrl = await this.getBaseUrl(instanceName);\n const params = this.encodeGetParams({\n limit,\n page,\n onlyActiveTags: true,\n specificTag,\n });\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/tag/?${params}`,\n )) as TagsResponse;\n }\n\n async getLabels(\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n ) {\n const baseUrl = await this.getBaseUrl(instanceName);\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/manifest/${digest}/labels`,\n )) as LabelsResponse;\n }\n\n async getManifestByDigest(\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n ) {\n const baseUrl = await this.getBaseUrl(instanceName);\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/manifest/${digest}`,\n )) as ManifestByDigestResponse;\n }\n\n async getSecurityDetails(\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n ) {\n const baseUrl = await this.getBaseUrl(instanceName);\n\n return (await this.fetcher(\n `${baseUrl}/repository/${org}/${repo}/manifest/${digest}/security`,\n )) as SecurityDetailsResponse;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { Progress, Table, Link } from '@backstage/core-components';
3
- import { useApi, configApiRef } from '@backstage/core-plugin-api';
3
+ import { useApi } from '@backstage/core-plugin-api';
4
4
  import { Box, Typography } from '@material-ui/core';
5
+ import { quayApiRef } from '../../api/index.esm.js';
5
6
  import { DOC_LINKS } from '../../doc-links.esm.js';
6
7
  import { useRepository, useTags } from '../../hooks/quay.esm.js';
7
8
  import { useQuayViewPermission } from '../../hooks/useQuayViewPermission.esm.js';
@@ -9,9 +10,10 @@ import PermissionAlert from '../PermissionAlert/PermissionAlert.esm.js';
9
10
  import { columns } from './tableHeading.esm.js';
10
11
 
11
12
  function QuayRepository(_props) {
12
- const { repository, organization } = useRepository();
13
- const configApi = useApi(configApiRef);
14
- const quayUiUrl = configApi.getOptionalString("quay.apiUrl") ?? configApi.getOptionalString("quay.uiUrl");
13
+ const { instanceName, repository, organization } = useRepository();
14
+ const quayApi = useApi(quayApiRef);
15
+ const instanceConfig = quayApi.getQuayInstance(instanceName);
16
+ const quayUiUrl = instanceConfig?.apiUrl ?? instanceConfig?.uiUrl;
15
17
  const hasViewPermission = useQuayViewPermission();
16
18
  const title = quayUiUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
17
19
  `Quay repository: `,
@@ -23,7 +25,7 @@ function QuayRepository(_props) {
23
25
  }
24
26
  )
25
27
  ] }) : `Quay repository: ${organization}/${repository}`;
26
- const { loading, data } = useTags(organization, repository);
28
+ const { loading, data } = useTags(instanceName, organization, repository);
27
29
  if (!hasViewPermission) {
28
30
  return /* @__PURE__ */ jsx(PermissionAlert, {});
29
31
  }
@@ -81,8 +83,22 @@ function QuayRepository(_props) {
81
83
  children: "2. Review the application logs in your Backstage instance"
82
84
  }
83
85
  ),
86
+ /* @__PURE__ */ jsxs(
87
+ Typography,
88
+ {
89
+ component: "p",
90
+ align: "center",
91
+ variant: "body2",
92
+ gutterBottom: true,
93
+ children: [
94
+ "3. Verify your entity annotations are",
95
+ " ",
96
+ /* @__PURE__ */ jsx(Link, { to: DOC_LINKS.BACKEND_ANNOTATIONS_GUIDE, children: "configured correctly" })
97
+ ]
98
+ }
99
+ ),
84
100
  /* @__PURE__ */ jsxs(Typography, { component: "p", align: "center", variant: "body2", children: [
85
- "3. Verify your",
101
+ "4. Verify your",
86
102
  " ",
87
103
  /* @__PURE__ */ jsx(Link, { to: DOC_LINKS.AUTH_TOKEN_GUIDE, children: "Quay access tokens" }),
88
104
  " ",
@@ -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 { 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":";;;;;;;;;;AA4BO,SAAS,eAAe,MAA6B,EAAA;AAC1D,EAAA,MAAM,EAAE,UAAA,EAAY,YAAa,EAAA,GAAI,aAAc,EAAA;AACnD,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,8BACG,IAAA,CAAA,GAAA,EAAA,EAAI,aAAY,EAAA,uBAAA,EAAwB,SAAS,CAChD,EAAA,QAAA,EAAA;AAAA,wBAAC,GAAA,CAAA,UAAA,EAAA,EAAW,WAAU,IAAK,EAAA,KAAA,EAAM,UAAS,OAAQ,EAAA,IAAA,EAAK,YAAY,EAAA,IAAA,EAAC,QAEpE,EAAA,2BAAA,EAAA,CAAA;AAAA,wBACA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,SAAU,EAAA,GAAA;AAAA,YACV,KAAM,EAAA,QAAA;AAAA,YACN,OAAQ,EAAA,OAAA;AAAA,YACR,KAAM,EAAA,eAAA;AAAA,YACN,YAAY,EAAA,IAAA;AAAA,YACb,QAAA,EAAA;AAAA;AAAA,SAGD;AAAA,wBACA,IAAA,CAAC,GAAI,EAAA,EAAA,EAAA,EAAI,CACP,EAAA,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,SAAU,EAAA,GAAA;AAAA,cACV,KAAM,EAAA,QAAA;AAAA,cACN,OAAQ,EAAA,OAAA;AAAA,cACR,YAAY,EAAA,IAAA;AAAA,cAEZ,QAAA,kBAAA,GAAA,CAAC,YAAO,QAAmB,EAAA,qBAAA,EAAA;AAAA;AAAA,WAC7B;AAAA,0BACA,GAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,SAAU,EAAA,GAAA;AAAA,cACV,KAAM,EAAA,QAAA;AAAA,cACN,OAAQ,EAAA,OAAA;AAAA,cACR,YAAY,EAAA,IAAA;AAAA,cACb,QAAA,EAAA;AAAA;AAAA,WAED;AAAA,0BACA,GAAA;AAAA,YAAC,UAAA;AAAA,YAAA;AAAA,cACC,SAAU,EAAA,GAAA;AAAA,cACV,KAAM,EAAA,QAAA;AAAA,cACN,OAAQ,EAAA,OAAA;AAAA,cACR,YAAY,EAAA,IAAA;AAAA,cACb,QAAA,EAAA;AAAA;AAAA,WAED;AAAA,+BACC,UAAW,EAAA,EAAA,SAAA,EAAU,KAAI,KAAM,EAAA,QAAA,EAAS,SAAQ,OAAQ,EAAA,QAAA,EAAA;AAAA,YAAA,gBAAA;AAAA,YACxC,GAAA;AAAA,4BACd,GAAA,CAAA,IAAA,EAAA,EAAK,EAAI,EAAA,SAAA,CAAU,kBAAkB,QAAkB,EAAA,oBAAA,EAAA,CAAA;AAAA,YAAQ,GAAA;AAAA,YAAI,KAAA;AAAA,YAChE,GAAA;AAAA,4BACH,GAAA,CAAA,IAAA,EAAA,EAAK,EAAI,EAAA,SAAA,CAAU,6BAA6B,QAEjD,EAAA,sBAAA,EAAA;AAAA,WACF,EAAA;AAAA,SACF,EAAA;AAAA,OACF,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 { useApi } from '@backstage/core-plugin-api';\n\nimport { Box, Typography } from '@material-ui/core';\n\nimport { quayApiRef } from '../../api';\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 { instanceName, repository, organization } = useRepository();\n const quayApi = useApi(quayApiRef);\n\n const instanceConfig = quayApi.getQuayInstance(instanceName);\n const quayUiUrl = instanceConfig?.apiUrl ?? instanceConfig?.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(instanceName, 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\n component=\"p\"\n align=\"center\"\n variant=\"body2\"\n gutterBottom\n >\n 3. Verify your entity annotations are{' '}\n <Link to={DOC_LINKS.BACKEND_ANNOTATIONS_GUIDE}>\n configured correctly\n </Link>\n </Typography>\n <Typography component=\"p\" align=\"center\" variant=\"body2\">\n 4. 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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"}
@@ -10,13 +10,18 @@ import { QuayTagDetails } from '../QuayTagDetails/component.esm.js';
10
10
 
11
11
  const QuayTagPage = () => {
12
12
  const rootLink = useRouteRef(rootRouteRef);
13
- const { repository, organization } = useRepository();
13
+ const { instanceName, repository, organization } = useRepository();
14
14
  const { digest } = useParams();
15
15
  const hasViewPermission = useQuayViewPermission();
16
16
  if (!digest) {
17
17
  throw new Error("digest is not defined");
18
18
  }
19
- const { loading, value } = useTagDetails(organization, repository, digest);
19
+ const { loading, value } = useTagDetails(
20
+ instanceName,
21
+ organization,
22
+ repository,
23
+ digest
24
+ );
20
25
  if (!hasViewPermission) {
21
26
  return /* @__PURE__ */ jsx(PermissionAlert, {});
22
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"component.esm.js","sources":["../../../src/components/QuayTagPage/component.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 { useParams } from 'react-router-dom';\n\nimport { ErrorPanel, Progress } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/core-plugin-api';\n\nimport { useRepository, useTagDetails } from '../../hooks';\nimport { useQuayViewPermission } from '../../hooks/useQuayViewPermission';\nimport { rootRouteRef } from '../../routes';\nimport PermissionAlert from '../PermissionAlert/PermissionAlert';\nimport { QuayTagDetails } from '../QuayTagDetails';\n\nexport const QuayTagPage = () => {\n const rootLink = useRouteRef(rootRouteRef);\n const { repository, organization } = useRepository();\n const { digest } = useParams();\n const hasViewPermission = useQuayViewPermission();\n if (!digest) {\n throw new Error('digest is not defined');\n }\n const { loading, value } = useTagDetails(organization, repository, digest);\n\n if (!hasViewPermission) {\n return <PermissionAlert />;\n }\n\n if (loading) {\n return (\n <div data-testid=\"quay-tag-page-progress\">\n <Progress variant=\"query\" />\n </div>\n );\n }\n if (!value?.data) {\n return <ErrorPanel error={new Error('no digest')} />;\n }\n\n return (\n <QuayTagDetails\n rootLink={rootLink}\n layer={value.data.Layer}\n digest={digest}\n />\n );\n};\n\nexport default QuayTagPage;\n"],"names":[],"mappings":";;;;;;;;;;AA0BO,MAAM,cAAc,MAAM;AAC/B,EAAM,MAAA,QAAA,GAAW,YAAY,YAAY,CAAA;AACzC,EAAA,MAAM,EAAE,UAAA,EAAY,YAAa,EAAA,GAAI,aAAc,EAAA;AACnD,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EAAA,MAAM,oBAAoB,qBAAsB,EAAA;AAChD,EAAA,IAAI,CAAC,MAAQ,EAAA;AACX,IAAM,MAAA,IAAI,MAAM,uBAAuB,CAAA;AAAA;AAEzC,EAAA,MAAM,EAAE,OAAS,EAAA,KAAA,KAAU,aAAc,CAAA,YAAA,EAAc,YAAY,MAAM,CAAA;AAEzE,EAAA,IAAI,CAAC,iBAAmB,EAAA;AACtB,IAAA,2BAAQ,eAAgB,EAAA,EAAA,CAAA;AAAA;AAG1B,EAAA,IAAI,OAAS,EAAA;AACX,IACE,uBAAA,GAAA,CAAC,SAAI,aAAY,EAAA,wBAAA,EACf,8BAAC,QAAS,EAAA,EAAA,OAAA,EAAQ,SAAQ,CAC5B,EAAA,CAAA;AAAA;AAGJ,EAAI,IAAA,CAAC,OAAO,IAAM,EAAA;AAChB,IAAA,2BAAQ,UAAW,EAAA,EAAA,KAAA,EAAO,IAAI,KAAA,CAAM,WAAW,CAAG,EAAA,CAAA;AAAA;AAGpD,EACE,uBAAA,GAAA;AAAA,IAAC,cAAA;AAAA,IAAA;AAAA,MACC,QAAA;AAAA,MACA,KAAA,EAAO,MAAM,IAAK,CAAA,KAAA;AAAA,MAClB;AAAA;AAAA,GACF;AAEJ;;;;"}
1
+ {"version":3,"file":"component.esm.js","sources":["../../../src/components/QuayTagPage/component.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 { useParams } from 'react-router-dom';\n\nimport { ErrorPanel, Progress } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/core-plugin-api';\n\nimport { useRepository, useTagDetails } from '../../hooks';\nimport { useQuayViewPermission } from '../../hooks/useQuayViewPermission';\nimport { rootRouteRef } from '../../routes';\nimport PermissionAlert from '../PermissionAlert/PermissionAlert';\nimport { QuayTagDetails } from '../QuayTagDetails';\n\nexport const QuayTagPage = () => {\n const rootLink = useRouteRef(rootRouteRef);\n const { instanceName, repository, organization } = useRepository();\n const { digest } = useParams();\n const hasViewPermission = useQuayViewPermission();\n if (!digest) {\n throw new Error('digest is not defined');\n }\n const { loading, value } = useTagDetails(\n instanceName,\n organization,\n repository,\n digest,\n );\n\n if (!hasViewPermission) {\n return <PermissionAlert />;\n }\n\n if (loading) {\n return (\n <div data-testid=\"quay-tag-page-progress\">\n <Progress variant=\"query\" />\n </div>\n );\n }\n if (!value?.data) {\n return <ErrorPanel error={new Error('no digest')} />;\n }\n\n return (\n <QuayTagDetails\n rootLink={rootLink}\n layer={value.data.Layer}\n digest={digest}\n />\n );\n};\n\nexport default QuayTagPage;\n"],"names":[],"mappings":";;;;;;;;;;AA0BO,MAAM,cAAc,MAAM;AAC/B,EAAM,MAAA,QAAA,GAAW,YAAY,YAAY,CAAA;AACzC,EAAA,MAAM,EAAE,YAAA,EAAc,UAAY,EAAA,YAAA,KAAiB,aAAc,EAAA;AACjE,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EAAA,MAAM,oBAAoB,qBAAsB,EAAA;AAChD,EAAA,IAAI,CAAC,MAAQ,EAAA;AACX,IAAM,MAAA,IAAI,MAAM,uBAAuB,CAAA;AAAA;AAEzC,EAAM,MAAA,EAAE,OAAS,EAAA,KAAA,EAAU,GAAA,aAAA;AAAA,IACzB,YAAA;AAAA,IACA,YAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,IAAI,CAAC,iBAAmB,EAAA;AACtB,IAAA,2BAAQ,eAAgB,EAAA,EAAA,CAAA;AAAA;AAG1B,EAAA,IAAI,OAAS,EAAA;AACX,IACE,uBAAA,GAAA,CAAC,SAAI,aAAY,EAAA,wBAAA,EACf,8BAAC,QAAS,EAAA,EAAA,OAAA,EAAQ,SAAQ,CAC5B,EAAA,CAAA;AAAA;AAGJ,EAAI,IAAA,CAAC,OAAO,IAAM,EAAA;AAChB,IAAA,2BAAQ,UAAW,EAAA,EAAA,KAAA,EAAO,IAAI,KAAA,CAAM,WAAW,CAAG,EAAA,CAAA;AAAA;AAGpD,EACE,uBAAA,GAAA;AAAA,IAAC,cAAA;AAAA,IAAA;AAAA,MACC,QAAA;AAAA,MACA,KAAA,EAAO,MAAM,IAAK,CAAA,KAAA;AAAA,MAClB;AAAA;AAAA,GACF;AAEJ;;;;"}
@@ -1,6 +1,7 @@
1
1
  const DOC_LINKS = {
2
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"
3
+ BACKEND_CONFIGURATION_GUIDE: "https://github.com/backstage/community-plugins/tree/main/workspaces/quay/plugins/quay-backend#app-config",
4
+ BACKEND_ANNOTATIONS_GUIDE: "https://github.com/backstage/community-plugins/tree/main/workspaces/quay/plugins/quay-backend#catalog"
4
5
  };
5
6
 
6
7
  export { DOC_LINKS };
@@ -1 +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;;;;"}
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 BACKEND_ANNOTATIONS_GUIDE:\n 'https://github.com/backstage/community-plugins/tree/main/workspaces/quay/plugins/quay-backend#catalog',\n} as const;\n"],"names":[],"mappings":"AAgBO,MAAM,SAAY,GAAA;AAAA,EACvB,gBACE,EAAA,8HAAA;AAAA,EACF,2BACE,EAAA,0GAAA;AAAA,EACF,yBACE,EAAA;AACJ;;;;"}
@@ -17,7 +17,7 @@ const useLocalStyles = makeStyles({
17
17
  }
18
18
  }
19
19
  });
20
- const useTags = (organization, repository) => {
20
+ const useTags = (instanceName, organization, repository) => {
21
21
  const quayClient = useApi(quayApiRef);
22
22
  const [tags, setTags] = useState([]);
23
23
  const [tagManifestLayers, setTagManifestLayers] = useState({});
@@ -25,6 +25,7 @@ const useTags = (organization, repository) => {
25
25
  const localClasses = useLocalStyles();
26
26
  const fetchSecurityDetails = async (tag) => {
27
27
  const securityDetails = await quayClient.getSecurityDetails(
28
+ instanceName,
28
29
  organization,
29
30
  repository,
30
31
  tag.manifest_digest
@@ -32,7 +33,13 @@ const useTags = (organization, repository) => {
32
33
  return securityDetails;
33
34
  };
34
35
  const { loading } = useAsync(async () => {
35
- const tagsResponse = await quayClient.getTags(organization, repository);
36
+ const tagsResponse = await quayClient.getTags(
37
+ instanceName,
38
+ organization,
39
+ repository,
40
+ undefined,
41
+ undefined
42
+ );
36
43
  Promise.all(
37
44
  tagsResponse.tags.map(async (tag) => {
38
45
  const securityDetails = await fetchSecurityDetails(tag);
@@ -82,28 +89,34 @@ const useTags = (organization, repository) => {
82
89
  return { loading, data };
83
90
  };
84
91
  const QUAY_ANNOTATION_REPOSITORY = "quay.io/repository-slug";
92
+ const QUAY_ANNOTATION_INSTANCE = "quay.io/instance-name";
85
93
  const useQuayAppData = ({ entity }) => {
94
+ const instanceSlug = entity?.metadata.annotations?.[QUAY_ANNOTATION_INSTANCE];
86
95
  const repositorySlug = entity?.metadata.annotations?.[QUAY_ANNOTATION_REPOSITORY] ?? "";
87
96
  if (!repositorySlug) {
88
97
  throw new Error("'Quay' annotations are missing");
89
98
  }
90
- return { repositorySlug };
99
+ return { instanceSlug, repositorySlug };
91
100
  };
92
101
  const useRepository = () => {
93
102
  const { entity } = useEntity();
94
- const { repositorySlug } = useQuayAppData({ entity });
103
+ const { instanceSlug: instanceName, repositorySlug } = useQuayAppData({
104
+ entity
105
+ });
95
106
  const info = repositorySlug.split("/");
96
107
  const organization = info.shift();
97
108
  const repository = info.join("/");
98
109
  return {
110
+ instanceName,
99
111
  organization,
100
112
  repository
101
113
  };
102
114
  };
103
- const useTagDetails = (org, repo, digest) => {
115
+ const useTagDetails = (instanceName, org, repo, digest) => {
104
116
  const quayClient = useApi(quayApiRef);
105
117
  const result = useAsync(async () => {
106
118
  const manifestLayer = await quayClient.getSecurityDetails(
119
+ instanceName,
107
120
  org,
108
121
  repo,
109
122
  digest
@@ -113,5 +126,5 @@ const useTagDetails = (org, repo, digest) => {
113
126
  return result;
114
127
  };
115
128
 
116
- export { QUAY_ANNOTATION_REPOSITORY, useQuayAppData, useRepository, useTagDetails, useTags };
129
+ export { QUAY_ANNOTATION_INSTANCE, QUAY_ANNOTATION_REPOSITORY, useQuayAppData, useRepository, useTagDetails, useTags };
117
130
  //# sourceMappingURL=quay.esm.js.map
@@ -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 { 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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"}
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 = (\n instanceName: string | undefined,\n organization: string,\n repository: string,\n) => {\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 instanceName,\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(\n instanceName,\n organization,\n repository,\n undefined,\n undefined,\n );\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';\nexport const QUAY_ANNOTATION_INSTANCE = 'quay.io/instance-name';\n\nexport const useQuayAppData = ({ entity }: { entity: Entity }) => {\n const instanceSlug = entity?.metadata.annotations?.[QUAY_ANNOTATION_INSTANCE];\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 { instanceSlug, repositorySlug };\n};\n\nexport const useRepository = () => {\n const { entity } = useEntity();\n const { instanceSlug: instanceName, repositorySlug } = useQuayAppData({\n entity,\n });\n const info = repositorySlug.split('/');\n\n const organization = info.shift() as 'string';\n const repository = info.join('/');\n return {\n instanceName,\n organization,\n repository,\n };\n};\n\nexport const useTagDetails = (\n instanceName: string | undefined,\n org: string,\n repo: string,\n digest: string,\n) => {\n const quayClient = useApi(quayApiRef);\n const result = useAsync(async () => {\n const manifestLayer = await quayClient.getSecurityDetails(\n instanceName,\n org,\n repo,\n digest,\n );\n return manifestLayer;\n });\n return result;\n};\n"],"names":[],"mappings":"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"}
@@ -17,7 +17,7 @@ const quayPlugin = createPlugin({
17
17
  configApi: configApiRef,
18
18
  identityApi: identityApiRef
19
19
  },
20
- factory: ({ discoveryApi, configApi, identityApi }) => new QuayApiClient({ discoveryApi, configApi, identityApi })
20
+ factory: ({ discoveryApi, configApi, identityApi }) => QuayApiClient.fromConfig({ discoveryApi, configApi, identityApi })
21
21
  })
22
22
  ]
23
23
  });
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.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 */\nimport { Entity } from '@backstage/catalog-model';\nimport {\n configApiRef,\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { QuayApiClient, quayApiRef } from './api';\nimport { QUAY_ANNOTATION_REPOSITORY } from './hooks';\nimport { rootRouteRef, tagRouteRef } from './routes';\n\n/**\n * Quay plugin\n *\n * @public\n */\nexport const quayPlugin = createPlugin({\n id: 'quay',\n routes: {\n root: rootRouteRef,\n tag: tagRouteRef,\n },\n apis: [\n createApiFactory({\n api: quayApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n configApi: configApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, configApi, identityApi }) =>\n new QuayApiClient({ discoveryApi, configApi, identityApi }),\n }),\n ],\n});\n\n/**\n * Quay page\n *\n * @public\n */\nexport const QuayPage = quayPlugin.provide(\n createRoutableExtension({\n name: 'QuayPage',\n component: () => import('./components/Router').then(m => m.Router),\n mountPoint: rootRouteRef,\n }),\n);\n\n/**\n * Returns true if the catalog entity contains the quay annotation `quay.io/repository-slug`.\n *\n * @public\n */\nexport const isQuayAvailable = (entity: Entity) =>\n Boolean(entity?.metadata.annotations?.[QUAY_ANNOTATION_REPOSITORY]);\n"],"names":[],"mappings":";;;;;AAkCO,MAAM,aAAa,YAAa,CAAA;AAAA,EACrC,EAAI,EAAA,MAAA;AAAA,EACJ,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,YAAA;AAAA,IACN,GAAK,EAAA;AAAA,GACP;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,UAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,SAAW,EAAA,YAAA;AAAA,QACX,WAAa,EAAA;AAAA,OACf;AAAA,MACA,OAAS,EAAA,CAAC,EAAE,YAAA,EAAc,SAAW,EAAA,WAAA,EACnC,KAAA,IAAI,aAAc,CAAA,EAAE,YAAc,EAAA,SAAA,EAAW,aAAa;AAAA,KAC7D;AAAA;AAEL,CAAC;AAOM,MAAM,WAAW,UAAW,CAAA,OAAA;AAAA,EACjC,uBAAwB,CAAA;AAAA,IACtB,IAAM,EAAA,UAAA;AAAA,IACN,SAAA,EAAW,MAAM,OAAO,4BAAqB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,MAAM,CAAA;AAAA,IACjE,UAAY,EAAA;AAAA,GACb;AACH;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.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 */\nimport { Entity } from '@backstage/catalog-model';\nimport {\n configApiRef,\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { QuayApiClient, quayApiRef } from './api';\nimport { QUAY_ANNOTATION_REPOSITORY } from './hooks';\nimport { rootRouteRef, tagRouteRef } from './routes';\n\n/**\n * Quay plugin\n *\n * @public\n */\nexport const quayPlugin = createPlugin({\n id: 'quay',\n routes: {\n root: rootRouteRef,\n tag: tagRouteRef,\n },\n apis: [\n createApiFactory({\n api: quayApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n configApi: configApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, configApi, identityApi }) =>\n QuayApiClient.fromConfig({ discoveryApi, configApi, identityApi }),\n }),\n ],\n});\n\n/**\n * Quay page\n *\n * @public\n */\nexport const QuayPage = quayPlugin.provide(\n createRoutableExtension({\n name: 'QuayPage',\n component: () => import('./components/Router').then(m => m.Router),\n mountPoint: rootRouteRef,\n }),\n);\n\n/**\n * Returns true if the catalog entity contains the quay annotation `quay.io/repository-slug`.\n *\n * @public\n */\nexport const isQuayAvailable = (entity: Entity) =>\n Boolean(entity?.metadata.annotations?.[QUAY_ANNOTATION_REPOSITORY]);\n"],"names":[],"mappings":";;;;;AAkCO,MAAM,aAAa,YAAa,CAAA;AAAA,EACrC,EAAI,EAAA,MAAA;AAAA,EACJ,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,YAAA;AAAA,IACN,GAAK,EAAA;AAAA,GACP;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,UAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,SAAW,EAAA,YAAA;AAAA,QACX,WAAa,EAAA;AAAA,OACf;AAAA,MACA,OAAS,EAAA,CAAC,EAAE,YAAA,EAAc,SAAW,EAAA,WAAA,EACnC,KAAA,aAAA,CAAc,UAAW,CAAA,EAAE,YAAc,EAAA,SAAA,EAAW,aAAa;AAAA,KACpE;AAAA;AAEL,CAAC;AAOM,MAAM,WAAW,UAAW,CAAA,OAAA;AAAA,EACjC,uBAAwB,CAAA;AAAA,IACtB,IAAM,EAAA,UAAA;AAAA,IACN,SAAA,EAAW,MAAM,OAAO,4BAAqB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,MAAM,CAAA;AAAA,IACjE,UAAY,EAAA;AAAA,GACb;AACH;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-quay",
3
- "version": "1.26.0",
3
+ "version": "1.28.0",
4
4
  "main": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -36,13 +36,13 @@
36
36
  "ui-test": "start-server-and-test start localhost:3000 'playwright test'"
37
37
  },
38
38
  "dependencies": {
39
- "@backstage-community/plugin-quay-common": "^1.13.0",
40
- "@backstage/catalog-model": "^1.7.5",
41
- "@backstage/core-components": "^0.18.2",
42
- "@backstage/core-plugin-api": "^1.11.1",
43
- "@backstage/plugin-catalog-common": "^1.1.6",
44
- "@backstage/plugin-catalog-react": "^1.21.2",
45
- "@backstage/plugin-permission-react": "^0.4.37",
39
+ "@backstage-community/plugin-quay-common": "^1.15.0",
40
+ "@backstage/catalog-model": "^1.7.6",
41
+ "@backstage/core-components": "^0.18.3",
42
+ "@backstage/core-plugin-api": "^1.12.0",
43
+ "@backstage/plugin-catalog-common": "^1.1.7",
44
+ "@backstage/plugin-catalog-react": "^1.21.3",
45
+ "@backstage/plugin-permission-react": "^0.4.38",
46
46
  "@backstage/theme": "^0.7.0",
47
47
  "@material-ui/core": "^4.12.2",
48
48
  "@material-ui/icons": "^4.11.3",
@@ -57,12 +57,13 @@
57
57
  "react-router-dom": "^6.0.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@backstage/cli": "^0.34.4",
61
- "@backstage/core-app-api": "^1.19.1",
62
- "@backstage/dev-utils": "^1.1.15",
63
- "@backstage/test-utils": "^1.7.12",
64
- "@backstage/ui": "^0.8.2",
65
- "@playwright/test": "^1.52.0",
60
+ "@axe-core/playwright": "^4.11.0",
61
+ "@backstage/cli": "^0.34.5",
62
+ "@backstage/core-app-api": "^1.19.2",
63
+ "@backstage/dev-utils": "^1.1.17",
64
+ "@backstage/test-utils": "^1.7.13",
65
+ "@backstage/ui": "^0.9.1",
66
+ "@playwright/test": "^1.56.1",
66
67
  "@testing-library/jest-dom": "^6.0.0",
67
68
  "@testing-library/react": "^15.0.0",
68
69
  "@types/react": "^18.2.58",