@camunda/ccma-saas-frontend 0.0.33 → 0.0.35

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.
Files changed (146) hide show
  1. package/lib/esm/components/c4-ai-usage/c4-ai-usage.types.d.ts +1 -1
  2. package/lib/esm/components/c4-billing-page/elements/current-usage-metrics.js +4 -2
  3. package/lib/esm/components/c4-billing-page/elements/value-metric.js +3 -3
  4. package/lib/esm/components/c4-billing-page/elements/value-metric.spec.d.ts +1 -0
  5. package/lib/esm/components/c4-billing-page/elements/value-metric.spec.js +49 -0
  6. package/lib/esm/components/c4-cluster-details-secure-connectivity/c4-cluster-details-secure-connectivity.d.ts +1 -1
  7. package/lib/esm/components/c4-cluster-details-secure-connectivity/c4-cluster-details-secure-connectivity.js +87 -38
  8. package/lib/esm/components/c4-cluster-list/c4-cluster-list.d.ts +1 -1
  9. package/lib/esm/components/c4-cluster-list/c4-cluster-list.js +107 -1
  10. package/lib/esm/components/c4-cluster-list/c4-cluster-list.spec.d.ts +1 -0
  11. package/lib/esm/components/c4-cluster-list/c4-cluster-list.spec.js +132 -0
  12. package/lib/esm/components/c4-cluster-overview/cluster-details.d.ts +1 -5
  13. package/lib/esm/components/c4-cluster-overview/cluster-details.js +4 -11
  14. package/lib/esm/components/c4-cluster-settings/elements/multi-tenancy.d.ts +2 -1
  15. package/lib/esm/components/c4-cluster-settings/elements/multi-tenancy.js +2 -2
  16. package/lib/esm/components/c4-clusters-empty-state/c4-clusters-empty-state.types.d.ts +1 -1
  17. package/lib/esm/components/c4-create-cluster/c4-create-cluster.js +36 -9
  18. package/lib/esm/components/c4-create-cluster/c4-create-cluster.spec.d.ts +1 -0
  19. package/lib/esm/components/c4-create-cluster/c4-create-cluster.spec.js +187 -0
  20. package/lib/esm/components/c4-create-cluster/c4-create-cluster.types.d.ts +5 -0
  21. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.d.ts +3 -0
  22. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.js +41 -0
  23. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.types.d.ts +10 -0
  24. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.types.js +6 -0
  25. package/lib/esm/components/c4-job-dashboard-details-page/c4-job-dashboard-details-page.d.ts +1 -0
  26. package/lib/esm/components/c4-job-dashboard-details-page/c4-job-dashboard-details-page.js +22 -9
  27. package/lib/esm/components/c4-job-dashboard-details-page/c4-job-dashboard-details-page.types.d.ts +3 -1
  28. package/lib/esm/components/c4-job-dashboard-details-page/elements/c4-job-dashboard-error-table.js +6 -3
  29. package/lib/esm/components/c4-job-dashboard-details-page/elements/c4-job-dashboard-piechart.js +5 -4
  30. package/lib/esm/components/c4-job-dashboard-details-page/elements/c4-job-dashboard-time-chart.js +22 -1
  31. package/lib/esm/components/c4-job-dashboard-details-page/elements/c4-job-dashboard-worker-table.js +6 -2
  32. package/lib/esm/components/c4-job-dashboard-entry-point/c4-job-dashboard-entry-point.js +17 -5
  33. package/lib/esm/components/c4-job-dashboard-entry-point/c4-job-dashboard-entry-point.types.d.ts +3 -1
  34. package/lib/esm/components/c4-job-dashboard-overview-page/c4-job-dashboard-overview.js +19 -6
  35. package/lib/esm/components/c4-job-dashboard-overview-page/c4-job-dashboard-overview.types.d.ts +4 -1
  36. package/lib/esm/components/c4-job-dashboard-shared/c4-job-dashboard-error-handler.d.ts +2 -6
  37. package/lib/esm/components/c4-job-dashboard-shared/c4-job-dashboard-error-handler.js +3 -3
  38. package/lib/esm/components/c4-job-dashboard-shared/c4-job-dashboard-shared.types.d.ts +1 -1
  39. package/lib/esm/components/c4-job-dashboard-shared/c4-job-dashboard-table-heading-with-tooltip.d.ts +1 -0
  40. package/lib/esm/components/c4-job-dashboard-shared/c4-job-dashboard-table-heading-with-tooltip.js +19 -0
  41. package/lib/esm/components/c4-llm-usage-meter/c4-llm-usage-meter.js +1 -1
  42. package/lib/esm/components/c4-llm-usage-meter/c4-llm-usage-meter.types.d.ts +3 -3
  43. package/lib/esm/components/c4-multi-step-modal-ugly/c4-multi-step-modal-ugly.d.ts +1 -1
  44. package/lib/esm/components/c4-multi-step-modal-ugly/c4-multi-step-modal-ugly.js +7 -8
  45. package/lib/esm/components/c4-multi-step-modal-ugly/c4-multi-step-modal-ugly.types.d.ts +5 -3
  46. package/lib/esm/components/c4-page-template/c4-page-template.spec.js +1 -1
  47. package/lib/esm/components/c4-search/c4-search-results.js +1 -1
  48. package/lib/esm/components/c4-search/c4-search.js +1 -1
  49. package/lib/esm/components/c4-sm-cluster-details/c4-sm-cluster-details.js +1 -1
  50. package/lib/esm/components/c4-tabs/c4-tabs.spec.js +1 -1
  51. package/lib/esm/components/c4-theme-switcher/c4-theme-switcher.d.ts +2 -0
  52. package/lib/esm/components/c4-theme-switcher/c4-theme-switcher.js +49 -6
  53. package/lib/esm/components/c4-theme-switcher/c4-theme-switcher.types.d.ts +1 -1
  54. package/lib/esm/components/c4-usage-history/elements/usage-history-charts.d.ts +2 -2
  55. package/lib/esm/components/c4-usage-history/elements/usage-history-charts.js +1 -1
  56. package/lib/esm/externalUrls.const.d.ts +90 -0
  57. package/lib/esm/externalUrls.const.js +191 -0
  58. package/lib/esm/externalUrls.const.spec.d.ts +1 -0
  59. package/lib/esm/externalUrls.const.spec.js +108 -0
  60. package/lib/esm/i18n/en/modals/clusters.json +46 -0
  61. package/lib/esm/i18n/en/pages/clusterdetails.json +52 -3
  62. package/lib/esm/i18n/en/pages/createcluster.json +3 -1
  63. package/lib/esm/i18n/en/pages/job-dashboard.json +13 -8
  64. package/lib/esm/i18n/en/pages/orgmanagement.json +4 -0
  65. package/lib/esm/index.d.ts +3 -0
  66. package/lib/esm/index.js +2 -0
  67. package/lib/esm/runtime/c4-console-authz.d.ts +2 -1
  68. package/lib/esm/runtime/c4-console-authz.js +29 -58
  69. package/lib/esm/runtime/c4-console-authz.spec.js +86 -0
  70. package/lib/esm/runtime/c4-console-data-context.js +9 -5
  71. package/lib/esm/runtime/c4-console-data-context.types.d.ts +5 -1
  72. package/lib/esm/runtime/c4-console-data-store.d.ts +5 -1
  73. package/lib/esm/runtime/c4-console-data-store.js +67 -2
  74. package/lib/esm/runtime/c4-console-data-store.spec.js +142 -0
  75. package/lib/esm/themes/future-carbon-light.css +64 -0
  76. package/lib/esm/themes/future-carbon.css +64 -0
  77. package/lib/esm/widgets/cluster-client-details/c4-cluster-client-details-app.js +3 -2
  78. package/lib/esm/widgets/cluster-client-details/c4-cluster-client-details-app.types.d.ts +2 -2
  79. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.js +247 -72
  80. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.permissions.spec.d.ts +1 -0
  81. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.permissions.spec.js +431 -0
  82. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.types.d.ts +71 -14
  83. package/lib/esm/widgets/cluster-details/c4-cluster-details-page.spec.js +1 -1
  84. package/lib/esm/widgets/cluster-details/c4-cluster-details-page.types.d.ts +1 -1
  85. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-alerts-tab.d.ts +8 -2
  86. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-alerts-tab.js +43 -10
  87. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-alerts-tab.spec.d.ts +1 -0
  88. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-alerts-tab.spec.js +72 -0
  89. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-byom-clients-tab.d.ts +1 -0
  90. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-byom-clients-tab.js +18 -7
  91. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-byom-clients-tab.spec.d.ts +1 -0
  92. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-byom-clients-tab.spec.js +85 -0
  93. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-clients-tab.d.ts +4 -1
  94. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-clients-tab.js +21 -17
  95. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-clients-tab.mcp.spec.js +1 -1
  96. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-clients-tab.spec.d.ts +1 -0
  97. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-clients-tab.spec.js +155 -0
  98. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-connector-secrets-tab.d.ts +6 -0
  99. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-connector-secrets-tab.js +5 -5
  100. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-encryption-tab.d.ts +5 -0
  101. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-encryption-tab.js +4 -4
  102. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-hot-backups-tab.d.ts +8 -4
  103. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-hot-backups-tab.js +59 -22
  104. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-hot-backups-tab.spec.d.ts +1 -0
  105. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-hot-backups-tab.spec.js +130 -0
  106. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-overview-tab.d.ts +14 -3
  107. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-overview-tab.js +453 -58
  108. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-overview-tab.spec.d.ts +1 -0
  109. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-overview-tab.spec.js +588 -0
  110. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-secure-connectivity-tab.d.ts +4 -0
  111. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-secure-connectivity-tab.js +281 -175
  112. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-secure-connectivity-tab.spec.d.ts +1 -1
  113. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-secure-connectivity-tab.spec.js +191 -6
  114. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-settings-tab.d.ts +8 -1
  115. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-settings-tab.js +304 -186
  116. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-settings-tab.spec.d.ts +1 -1
  117. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-settings-tab.spec.js +184 -10
  118. package/lib/esm/widgets/clusters/c4-clusters-page.js +5 -3
  119. package/lib/esm/widgets/clusters/c4-clusters-page.spec.js +39 -4
  120. package/lib/esm/widgets/connector-management/c4-connector-management-executable-details-page.js +1 -1
  121. package/lib/esm/widgets/connector-management/c4-connector-management-executables-table-page.js +1 -1
  122. package/lib/esm/widgets/connector-management/c4-connector-management-page.js +1 -1
  123. package/lib/esm/widgets/create-cluster/c4-create-cluster-page.js +27 -10
  124. package/lib/esm/widgets/create-cluster/c4-create-cluster-page.spec.js +14 -2
  125. package/lib/esm/widgets/create-cluster/c4-create-cluster-page.types.d.ts +5 -3
  126. package/lib/esm/widgets/dashboard/c4-dashboard-app.js +8 -7
  127. package/lib/esm/widgets/dashboard/c4-dashboard-app.types.d.ts +10 -9
  128. package/lib/esm/widgets/group-details/c4-group-details-page.js +2 -3
  129. package/lib/esm/widgets/group-details/c4-group-details-page.spec.js +1 -1
  130. package/lib/esm/widgets/group-details/c4-group-details-page.types.d.ts +1 -1
  131. package/lib/esm/widgets/management/c4-management-app.d.ts +1 -1
  132. package/lib/esm/widgets/management/c4-management-app.js +345 -91
  133. package/lib/esm/widgets/management/c4-management-app.spec.d.ts +1 -0
  134. package/lib/esm/widgets/management/c4-management-app.spec.js +347 -0
  135. package/lib/esm/widgets/management/c4-management-app.types.d.ts +25 -14
  136. package/lib/esm/widgets/management/tabs/c4-management-billing-tab.d.ts +1 -0
  137. package/lib/esm/widgets/management/tabs/c4-management-billing-tab.js +9 -1
  138. package/lib/esm/widgets/management/tabs/c4-management-usage-tab.d.ts +2 -1
  139. package/lib/esm/widgets/management/tabs/c4-management-usage-tab.js +22 -19
  140. package/lib/esm/widgets/management/tabs/c4-management-users-tab.d.ts +13 -1
  141. package/lib/esm/widgets/management/tabs/c4-management-users-tab.js +172 -21
  142. package/lib/esm/widgets/management/tabs/c4-management-users-tab.spec.js +63 -1
  143. package/lib/esm/widgets/user-details/c4-user-details-page.js +2 -3
  144. package/lib/esm/widgets/user-details/c4-user-details-page.spec.js +6 -2
  145. package/lib/esm/widgets/user-details/c4-user-details-page.types.d.ts +1 -1
  146. package/package.json +15 -15
@@ -46,7 +46,7 @@ export interface C4AiUsageProps {
46
46
  urls: {
47
47
  blueprints: string;
48
48
  llmTokensDocs: string;
49
- contact?: string;
49
+ contact: string;
50
50
  };
51
51
  /**
52
52
  * Callback when the "View AI blueprints" button is clicked
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /*
3
3
  * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
4
4
  * under one or more contributor license agreements. Licensed under a commercial license.
@@ -38,7 +38,9 @@ export const CurrentUsageMetrics = ({ title, decisionInstances, description, gen
38
38
  break;
39
39
  }
40
40
  }
41
- const component = (_jsx(C4TileWithTitleAndAction, { title: title, description: description, children: ['sm', 'md'].includes(mediaSize) ? (_jsxs(Stack, { gap: 5, children: [_jsxs(Grid, { style: { ...STYLES.grid }, children: [_jsx(Column, { span: 8, style: { ...STYLES.grid }, children: metrics[0] }), _jsx(Column, { span: 8, style: { ...STYLES.grid }, children: metrics[1] })] }), _jsxs(Grid, { style: { ...STYLES.grid }, children: [_jsx(Column, { span: 8, style: { ...STYLES.grid }, children: metrics[2] }), _jsx(Column, { span: 8, style: { ...STYLES.grid }, children: metrics[3] })] })] })) : (_jsx(Grid, { style: { ...STYLES.grid }, children: metrics.map((metric, index) => (_jsx(Column, { span: 4, style: { ...STYLES.grid }, children: metric }, `metric-${metricsOrder[index]}`))) })) }));
41
+ const component = (_jsx(C4TileWithTitleAndAction, { title: title, description: description, children: ['sm', 'md'].includes(mediaSize) ? (_jsx(Stack, { gap: 5, children: Array.from({ length: Math.ceil(metrics.length / 2) }, (_, rowIndex) => (_jsx(Grid, { style: { ...STYLES.grid }, children: metrics
42
+ .slice(rowIndex * 2, rowIndex * 2 + 2)
43
+ .map((metric, colIndex) => (_jsx(Column, { span: 8, style: { ...STYLES.grid }, children: metric }, `metric-${metricsOrder[rowIndex * 2 + colIndex]}`))) }, `row-${rowIndex}`))) })) : (_jsx(Grid, { style: { ...STYLES.grid }, children: metrics.map((metric, index) => (_jsx(Column, { span: 4, style: { ...STYLES.grid }, children: metric }, `metric-${metricsOrder[index]}`))) })) }));
42
44
  return component;
43
45
  };
44
46
  export function mediaSizeByWidth(width) {
@@ -103,14 +103,14 @@ const MeterVisualization = ({ currentPrefix, current, currentPostfix, max, maxPo
103
103
  display: 'grid',
104
104
  gridTemplateColumns: '1fr auto',
105
105
  width: '100%',
106
- }, children: [_jsx("p", { style: { fontSize: '18px' }, children: data.current }), _jsx("p", { style: { color: 'var(--cds-text-helper)', alignSelf: 'end' }, children: data.max })] }), _jsx(MeterChart, { data: [
106
+ }, children: [_jsx("p", { style: { fontSize: '18px' }, children: data.current }), _jsx("p", { style: { color: 'var(--cds-text-helper)', alignSelf: 'end' }, children: data.max })] }), max > 0 && (_jsx(MeterChart, { data: [
107
107
  {
108
108
  group,
109
109
  value: current,
110
110
  },
111
- ], options: chartOptions })] }));
111
+ ], options: chartOptions }))] }));
112
112
  return component;
113
113
  };
114
114
  function formatNumber(number) {
115
- return new Intl.NumberFormat('en-US', { maximumSignificantDigits: 3 }).format(number);
115
+ return new Intl.NumberFormat('en-US').format(number);
116
116
  }
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
4
+ * under one or more contributor license agreements. Licensed under a commercial license.
5
+ * You may not use this file except in compliance with the commercial license.
6
+ */
7
+ import { render, screen } from '@testing-library/react';
8
+ import { vi } from 'vitest';
9
+ import { ValueMetric } from './value-metric.js';
10
+ vi.mock('@carbon/charts-react', () => ({
11
+ MeterChart: ({ data }) => (_jsx("div", { "data-testid": 'meter-chart', "data-value": data[0]?.value })),
12
+ }));
13
+ function buildChartProps(current, max = 500000) {
14
+ return {
15
+ title: 'Process instances',
16
+ loading: false,
17
+ metric: {
18
+ kind: 'chart',
19
+ value: {
20
+ current,
21
+ max,
22
+ },
23
+ },
24
+ };
25
+ }
26
+ describe('ValueMetric', () => {
27
+ describe('MeterChart (progress bar)', () => {
28
+ it('renders the progress bar when included (max) > 0', () => {
29
+ render(_jsx(ValueMetric, { ...buildChartProps(0, 500000) }));
30
+ expect(screen.getByTestId('meter-chart')).toBeInTheDocument();
31
+ });
32
+ it('renders the progress bar when both current and max are > 0', () => {
33
+ render(_jsx(ValueMetric, { ...buildChartProps(400000, 500000) }));
34
+ expect(screen.getByTestId('meter-chart')).toBeInTheDocument();
35
+ });
36
+ it('does NOT render the progress bar when included (max) is zero', () => {
37
+ render(_jsx(ValueMetric, { ...buildChartProps(0, 0) }));
38
+ expect(screen.queryByTestId('meter-chart')).not.toBeInTheDocument();
39
+ });
40
+ it('does NOT render the progress bar when max is zero even if current > 0', () => {
41
+ render(_jsx(ValueMetric, { ...buildChartProps(5, 0) }));
42
+ expect(screen.queryByTestId('meter-chart')).not.toBeInTheDocument();
43
+ });
44
+ it('still renders the current and max values when included (max) is zero', () => {
45
+ render(_jsx(ValueMetric, { ...buildChartProps(0, 0) }));
46
+ expect(screen.getAllByText('0')).toHaveLength(2);
47
+ });
48
+ });
49
+ });
@@ -1,3 +1,3 @@
1
- import type { JSX } from 'react';
1
+ import { type JSX } from 'react';
2
2
  import type { C4ClusterDetailsSecureConnectivityProps } from './c4-cluster-details-secure-connectivity.types';
3
3
  export declare const C4ClusterDetailsSecureConnectivity: ({ status, privateEndpoints, connectInAwsUrl, isLoading, isDeactivating, text, permissions, urls, onOpenActivate, onOpenPrivateEndpoint, onOpenDocs, onDeactivate, onOpenConnectInAws, onEditAllowedPrincipals, onEditAllowedRegions, }: C4ClusterDetailsSecureConnectivityProps) => JSX.Element;
@@ -7,6 +7,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  import { C3DataTable, C3EmptyState, } from '@camunda/camunda-composite-components';
8
8
  import { Button, CodeSnippet, Layer, Link, Stack, StructuredListBody, StructuredListCell, StructuredListRow, StructuredListWrapper, Tile, } from '@carbon/react';
9
9
  import { CheckmarkFilled, Edit, Launch, PendingFilled, } from '@carbon/react/icons/index.esm.js';
10
+ import { useEffect, useState } from 'react';
11
+ const singleSnippetMaxWidth = '48rem';
10
12
  export const C4ClusterDetailsSecureConnectivity = ({ status, privateEndpoints, connectInAwsUrl, isLoading, isDeactivating, text, permissions, urls, onOpenActivate, onOpenPrivateEndpoint, onOpenDocs, onDeactivate, onOpenConnectInAws, onEditAllowedPrincipals, onEditAllowedRegions, }) => {
11
13
  const endpointRows = (privateEndpoints ?? []).map((endpoint, index) => ({
12
14
  id: endpoint.id,
@@ -102,15 +104,20 @@ export const C4ClusterDetailsSecureConnectivity = ({ status, privateEndpoints, c
102
104
  if (!value) {
103
105
  return _jsx("span", { children: "\u2014" });
104
106
  }
105
- return (_jsx(Layer, { style: { width: '100%', minWidth: 0 }, children: _jsx(CodeSnippet, { type: 'single', children: value }) }));
107
+ return (_jsx(Layer, { style: { width: '100%', maxWidth: singleSnippetMaxWidth, minWidth: 0 }, children: _jsx(CodeSnippet, { type: 'single', children: value }) }));
106
108
  };
107
109
  const renderListFieldWithEdit = (options) => {
108
110
  const values = options.values ?? [];
109
111
  return (_jsxs("div", { style: {
110
112
  display: 'flex',
113
+ width: '100%',
114
+ minWidth: 0,
115
+ maxWidth: options.alignToSingleSnippet
116
+ ? singleSnippetMaxWidth
117
+ : undefined,
111
118
  gap: 'var(--cds-spacing-02)',
112
119
  alignItems: values.length > 1 ? 'flex-start' : 'center',
113
- }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: values.length ? (_jsx(Stack, { gap: 1, children: values.map((value) => (_jsx(CodeSnippet, { type: 'inline', align: 'top', children: value }, value))) })) : (_jsx("span", { children: "\u2014" })) }), options.onEdit ? (_jsx(Button, { kind: 'ghost', size: 'sm', hasIconOnly: true, renderIcon: Edit, iconDescription: options.editLabel, style: { marginRight: options.marginRight ? '28px' : '4px' }, onClick: () => options.onEdit?.() })) : undefined] }));
120
+ }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: values.length ? (_jsx(Stack, { gap: 1, children: values.map((value) => (_jsx(CodeSnippet, { type: 'inline', align: 'top', children: value }, value))) })) : (_jsx("span", { children: "\u2014" })) }), options.onEdit ? (_jsx(Button, { kind: 'ghost', size: 'md', hasIconOnly: true, renderIcon: Edit, iconDescription: options.editLabel, onClick: () => options.onEdit?.() })) : undefined] }));
114
121
  };
115
122
  const labelCellLeftStyle = {
116
123
  width: '12rem',
@@ -121,11 +128,43 @@ export const C4ClusterDetailsSecureConnectivity = ({ status, privateEndpoints, c
121
128
  color: 'var(--cds-text-secondary)',
122
129
  paddingLeft: '40px',
123
130
  };
131
+ const helperText01Style = {
132
+ fontSize: 'var(--cds-helper-text-01-font-size)',
133
+ fontWeight: 'var(--cds-helper-text-01-font-weight)',
134
+ lineHeight: 'var(--cds-helper-text-01-line-height)',
135
+ letterSpacing: 'var(--cds-helper-text-01-letter-spacing)',
136
+ color: 'var(--cds-text-secondary)',
137
+ };
138
+ // Responsive layout: switch to single-column when the viewport is narrower
139
+ // than 1520px (the point at which the 2-column layout starts breaking).
140
+ // Measuring window.innerWidth is simpler and more reliable than observing
141
+ // the container element: the content tile sits inside Carbon's 1584px max
142
+ // grid with a sidebar, so the container never reaches the element-width
143
+ // thresholds that would be needed to detect this breakpoint.
144
+ const [isNarrowLayout, setIsNarrowLayout] = useState(() => typeof window !== 'undefined' && window.innerWidth < 1520);
145
+ useEffect(() => {
146
+ const handleResize = () => setIsNarrowLayout(window.innerWidth < 1520);
147
+ window.addEventListener('resize', handleResize);
148
+ return () => window.removeEventListener('resize', handleResize);
149
+ }, []);
150
+ const deactivateLinkElement = onDeactivate ? (_jsx(Link, { href: '#', "aria-disabled": !permissions.canDeactivate || !!isDeactivating, tabIndex: !permissions.canDeactivate || isDeactivating ? -1 : undefined, onClick: (e) => {
151
+ e.preventDefault();
152
+ if (permissions.canDeactivate && !isDeactivating) {
153
+ onDeactivate();
154
+ }
155
+ }, style: !permissions.canDeactivate || isDeactivating
156
+ ? {
157
+ color: 'var(--cds-text-disabled)',
158
+ pointerEvents: 'none',
159
+ }
160
+ : undefined, children: isDeactivating
161
+ ? text.deactivatingButton
162
+ : (text.deactivateButton ?? 'Deactivate service') })) : undefined;
124
163
  if (!isReady) {
125
164
  return (_jsx(C3EmptyState, { icon: {
126
165
  path: `../../../../assets/secureConnectivity.svg`,
127
166
  altText: 'secureConnectivityIcon',
128
- }, heading: text.emptyTitle, description: text.emptyDescription, button: status?.status !== undefined
167
+ }, heading: text.emptyTitle, description: text.emptyDescription, button: isLoading || status?.status !== undefined
129
168
  ? undefined
130
169
  : {
131
170
  onClick: () => onOpenActivate(),
@@ -142,47 +181,57 @@ export const C4ClusterDetailsSecureConnectivity = ({ status, privateEndpoints, c
142
181
  display: 'flex',
143
182
  alignItems: 'center',
144
183
  gap: 'var(--cds-spacing-05)',
145
- }, children: _jsx("h4", { style: { margin: 0 }, children: text.serviceDetailsTitle ?? 'Service details' }) }), _jsx(StructuredListWrapper, { isCondensed: true, isFlush: true, style: { borderTop: 'none' }, children: _jsxs(StructuredListBody, { children: [_jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.enabledStatusLabel ?? 'Status' }), _jsx(StructuredListCell, { style: { borderTop: 'none' }, children: statusElement }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: _jsx("span", { style: { opacity: 0 }, children: "\u2014" }) }), _jsx(StructuredListCell, { style: {
146
- borderTop: 'none',
147
- textAlign: 'right',
148
- }, children: onDeactivate ? (_jsx(Link, { href: '#', "aria-disabled": !permissions.canDeactivate || !!isDeactivating, tabIndex: !permissions.canDeactivate || isDeactivating
149
- ? -1
150
- : undefined, onClick: (e) => {
151
- e.preventDefault();
152
- if (permissions.canDeactivate && !isDeactivating) {
153
- onDeactivate();
154
- }
155
- }, style: !permissions.canDeactivate || isDeactivating
156
- ? {
157
- color: 'var(--cds-text-disabled)',
158
- pointerEvents: 'none',
159
- }
160
- : undefined, children: isDeactivating
161
- ? text.deactivatingButton
162
- : (text.deactivateButton ?? 'Deactivate service') })) : undefined })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceNameLabel ?? 'Service name' }), _jsx(StructuredListCell, { style: {
163
- minWidth: 0,
164
- paddingRight: '40px',
165
- }, children: renderInlineCodeField(endpoint?.serviceName) }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: text.serviceRegionLabel ?? 'Service region' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.region) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceTypeLabel ?? 'Service type' }), _jsx(StructuredListCell, { style: {
166
- minWidth: 0,
167
- paddingRight: '40px',
168
- }, children: renderInlineCodeField(endpoint?.type) }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: text.privateDnsNameLabel ?? 'Private DNS name' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.privateDnsName) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.allowedPrincipalsLabel }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderListFieldWithEdit({
169
- values: status?.spec.allowedPrincipals,
170
- onEdit: onEditAllowedPrincipals,
171
- editLabel: 'Edit allowed principals',
172
- marginRight: true,
173
- }) }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: text.allowedRegionsLabel }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderListFieldWithEdit({
174
- values: status?.spec.allowedRegions,
175
- onEdit: onEditAllowedRegions,
176
- editLabel: 'Edit supported regions',
177
- }) })] })] }) }), _jsx("div", { children: _jsxs(Stack, { gap: 3, children: [_jsx("h4", { style: { margin: 0, marginTop: '12px' }, children: text.connectTitle ?? 'Connect to the service' }), connectInAwsUrl ? (_jsx(Button, { kind: 'tertiary', renderIcon: Launch, style: { whiteSpace: 'nowrap', maxWidth: '100%' }, onClick: () => {
184
+ }, children: _jsx("h4", { style: { margin: 0 }, children: text.serviceDetailsTitle ?? 'Service details' }) }), _jsx("div", { children: isNarrowLayout ? (
185
+ // Single-column layout: each field on its own row
186
+ _jsx(StructuredListWrapper, { isCondensed: true, isFlush: true, style: { borderTop: 'none' }, children: _jsxs(StructuredListBody, { children: [_jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.enabledStatusLabel ?? 'Status' }), _jsx(StructuredListCell, { style: { borderTop: 'none' }, children: _jsxs("div", { style: {
187
+ display: 'flex',
188
+ justifyContent: 'space-between',
189
+ alignItems: 'center',
190
+ }, children: [statusElement, deactivateLinkElement] }) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceNameLabel ?? 'Service name' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.serviceName) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceRegionLabel ?? 'Service region' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.region) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceTypeLabel ?? 'Service type' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.type) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.privateDnsNameLabel ?? 'Private DNS name' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.privateDnsName) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.allowedPrincipalsLabel }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderListFieldWithEdit({
191
+ values: status?.spec.allowedPrincipals,
192
+ onEdit: onEditAllowedPrincipals,
193
+ editLabel: 'Edit allowed principals',
194
+ alignToSingleSnippet: true,
195
+ }) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.allowedRegionsLabel }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderListFieldWithEdit({
196
+ values: status?.spec.allowedRegions,
197
+ onEdit: onEditAllowedRegions,
198
+ editLabel: 'Edit supported regions',
199
+ alignToSingleSnippet: true,
200
+ }) })] })] }) })) : (
201
+ // Two-column layout: pairs of fields side-by-side
202
+ _jsx(StructuredListWrapper, { isCondensed: true, isFlush: true, style: { borderTop: 'none', overflowX: 'auto' }, children: _jsxs(StructuredListBody, { children: [_jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.enabledStatusLabel ?? 'Status' }), _jsx(StructuredListCell, { style: { borderTop: 'none' }, children: statusElement }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: _jsx("span", { style: { opacity: 0 }, children: "\u2014" }) }), _jsx(StructuredListCell, { style: {
203
+ borderTop: 'none',
204
+ textAlign: 'right',
205
+ }, children: deactivateLinkElement })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceNameLabel ?? 'Service name' }), _jsx(StructuredListCell, { style: {
206
+ minWidth: 0,
207
+ paddingRight: '40px',
208
+ }, children: renderInlineCodeField(endpoint?.serviceName) }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: text.serviceRegionLabel ?? 'Service region' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.region) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.serviceTypeLabel ?? 'Service type' }), _jsx(StructuredListCell, { style: {
209
+ minWidth: 0,
210
+ paddingRight: '40px',
211
+ }, children: renderInlineCodeField(endpoint?.type) }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: text.privateDnsNameLabel ?? 'Private DNS name' }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderInlineCodeField(endpoint?.privateDnsName) })] }), _jsxs(StructuredListRow, { children: [_jsx(StructuredListCell, { noWrap: true, style: labelCellLeftStyle, children: text.allowedPrincipalsLabel }), _jsx(StructuredListCell, { style: {
212
+ minWidth: 0,
213
+ paddingRight: '40px',
214
+ }, children: renderListFieldWithEdit({
215
+ values: status?.spec.allowedPrincipals,
216
+ onEdit: onEditAllowedPrincipals,
217
+ editLabel: 'Edit allowed principals',
218
+ }) }), _jsx(StructuredListCell, { noWrap: true, style: labelCellRightStyle, children: text.allowedRegionsLabel }), _jsx(StructuredListCell, { style: { minWidth: 0 }, children: renderListFieldWithEdit({
219
+ values: status?.spec.allowedRegions,
220
+ onEdit: onEditAllowedRegions,
221
+ editLabel: 'Edit supported regions',
222
+ }) })] })] }) })) }), _jsx("div", { children: _jsxs(Stack, { gap: 3, children: [_jsx("h4", { style: { margin: 0, marginTop: '12px' }, children: text.connectTitle ?? 'Connect to the service' }), connectInAwsUrl ? (_jsx(Button, { kind: 'tertiary', size: 'md', renderIcon: Launch, style: { whiteSpace: 'nowrap', maxWidth: '100%' }, onClick: () => {
178
223
  if (onOpenConnectInAws) {
179
224
  onOpenConnectInAws(connectInAwsUrl);
180
225
  }
181
226
  }, disabled: !onOpenConnectInAws, children: text.connectInAwsButtonLabel ??
182
- 'Create interface VPC endpoint connections in AWS' })) : undefined, text.connectHelperText ? (_jsx("div", { style: { color: 'var(--cds-text-secondary)' }, children: text.connectHelperText })) : undefined] }) }), _jsxs("div", { children: [_jsx("div", { style: {
227
+ 'Create interface VPC endpoint connections in AWS' })) : undefined, text.connectHelperText ? (_jsx("div", { style: helperText01Style, children: text.connectHelperText })) : undefined] }) }), _jsxs("div", { children: [_jsx("div", { style: {
183
228
  borderTop: '1px solid var(--cds-border-subtle-01)',
184
229
  paddingTop: 'var(--cds-spacing-05)',
185
- } }), _jsxs("h4", { style: { margin: 0, marginTop: '12px', marginBottom: '20px' }, children: [text.endpointConnectionsTitle ?? 'Endpoint connections', " (", endpointConnectionsCount, ")"] }), endpointRows.length === 0 && !isLoading ? (_jsx("div", { style: {
230
+ } }), _jsxs("h4", { style: {
231
+ margin: 0,
232
+ marginTop: '12px',
233
+ marginBottom: 'var(--cds-spacing-03)',
234
+ }, children: [text.endpointConnectionsTitle ?? 'Endpoint connections', " (", endpointConnectionsCount, ")"] }), endpointRows.length === 0 && !isLoading ? (_jsx("div", { style: {
186
235
  color: 'var(--cds-text-secondary)',
187
236
  marginLeft: '24px',
188
237
  marginBottom: '16px',
@@ -1,3 +1,3 @@
1
- import type { JSX } from 'react';
1
+ import { type JSX } from 'react';
2
2
  import type { C4ClusterListProps } from './c4-cluster-list.types';
3
3
  export declare const C4ClusterList: ({ clusters, loading, urls, permissions, onNavigate, onDeleteCluster, onAssignTag, onWakeCluster, trialNotification, encryptionNotification, clusterCapMessage, emptyStateContent, translations, isClusterPaused, isRunningOnUnsupportedGeneration, }: C4ClusterListProps) => JSX.Element;
@@ -7,10 +7,16 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
7
7
  import { C3ClusterTag, C3DataTable, } from '@camunda/camunda-composite-components';
8
8
  import { Button, InlineNotification, Link } from '@carbon/react';
9
9
  import { Add, WarningAltFilled } from '@carbon/react/icons/index.esm.js';
10
+ import { useCallback, useEffect, useLayoutEffect, useRef, } from 'react';
10
11
  import { ClusterStatusElement } from '../c4-cluster-overview/applications.js';
11
12
  import { C4IconAndTextElement, getClusterLoadConfig, } from '../c4-cluster-overview/c4-cluster-overview.js';
12
13
  import { C4LinkButton } from '../c4-link-button/c4-link-button.js';
13
14
  import { C4PageTemplate } from '../c4-page-template/c4-page-template.js';
15
+ const DEFAULT_PAGE_SIZE = 10;
16
+ const PAGE_NUMBER_SELECTOR = '.cds--select__page-number select';
17
+ const PAGE_SIZE_SELECTOR = '.cds--select__item-count select';
18
+ const PAGE_FORWARD_BUTTON_SELECTOR = '.cds--pagination__button--forward';
19
+ const PAGE_BACKWARD_BUTTON_SELECTOR = '.cds--pagination__button--backward';
14
20
  function isPlainLeftClick(event) {
15
21
  return (event.button === 0 &&
16
22
  !event.defaultPrevented &&
@@ -20,6 +26,106 @@ function isPlainLeftClick(event) {
20
26
  !event.shiftKey);
21
27
  }
22
28
  export const C4ClusterList = ({ clusters, loading, urls, permissions, onNavigate, onDeleteCluster, onAssignTag, onWakeCluster, trialNotification, encryptionNotification, clusterCapMessage, emptyStateContent, translations, isClusterPaused, isRunningOnUnsupportedGeneration, }) => {
29
+ const tableContainerRef = useRef(null);
30
+ const stableClustersRef = useRef(clusters.map((cluster) => ({ ...cluster })));
31
+ const selectedPageRef = useRef(1);
32
+ const syncPageTimeoutRef = useRef(undefined);
33
+ const stableClusters = (() => {
34
+ const previousClusters = stableClustersRef.current;
35
+ const canReusePreviousClusters = previousClusters.length === clusters.length &&
36
+ previousClusters.every((cluster, index) => cluster.id === clusters[index]?.id);
37
+ if (canReusePreviousClusters) {
38
+ clusters.forEach((cluster, index) => {
39
+ Object.assign(previousClusters[index], cluster);
40
+ });
41
+ return previousClusters;
42
+ }
43
+ const nextClusters = clusters.map((cluster) => ({ ...cluster }));
44
+ stableClustersRef.current = nextClusters;
45
+ return nextClusters;
46
+ })();
47
+ const readPageSelect = useCallback(() => {
48
+ return (tableContainerRef.current?.querySelector(PAGE_NUMBER_SELECTOR) ?? null);
49
+ }, []);
50
+ const readPageSizeSelect = useCallback(() => {
51
+ return (tableContainerRef.current?.querySelector(PAGE_SIZE_SELECTOR) ?? null);
52
+ }, []);
53
+ const syncSelectedPageFromDom = useCallback(() => {
54
+ const pageSelect = readPageSelect();
55
+ if (!pageSelect) {
56
+ selectedPageRef.current = 1;
57
+ return;
58
+ }
59
+ const page = Number(pageSelect.value);
60
+ if (Number.isFinite(page) && page > 0) {
61
+ selectedPageRef.current = page;
62
+ }
63
+ }, [readPageSelect]);
64
+ const scheduleSelectedPageSync = useCallback(() => {
65
+ if (typeof window === 'undefined') {
66
+ return;
67
+ }
68
+ if (syncPageTimeoutRef.current !== undefined) {
69
+ window.clearTimeout(syncPageTimeoutRef.current);
70
+ }
71
+ syncPageTimeoutRef.current = window.setTimeout(() => {
72
+ syncSelectedPageFromDom();
73
+ syncPageTimeoutRef.current = undefined;
74
+ }, 0);
75
+ }, [syncSelectedPageFromDom]);
76
+ useEffect(() => {
77
+ const tableContainer = tableContainerRef.current;
78
+ if (!tableContainer) {
79
+ return;
80
+ }
81
+ const pageSelect = tableContainer.querySelector(PAGE_NUMBER_SELECTOR);
82
+ const forwardButton = tableContainer.querySelector(PAGE_FORWARD_BUTTON_SELECTOR);
83
+ const backwardButton = tableContainer.querySelector(PAGE_BACKWARD_BUTTON_SELECTOR);
84
+ pageSelect?.addEventListener('change', syncSelectedPageFromDom);
85
+ forwardButton?.addEventListener('click', scheduleSelectedPageSync);
86
+ backwardButton?.addEventListener('click', scheduleSelectedPageSync);
87
+ return () => {
88
+ pageSelect?.removeEventListener('change', syncSelectedPageFromDom);
89
+ forwardButton?.removeEventListener('click', scheduleSelectedPageSync);
90
+ backwardButton?.removeEventListener('click', scheduleSelectedPageSync);
91
+ };
92
+ }, [scheduleSelectedPageSync, syncSelectedPageFromDom, stableClusters.length]);
93
+ useEffect(() => {
94
+ return () => {
95
+ if (typeof window !== 'undefined' &&
96
+ syncPageTimeoutRef.current !== undefined) {
97
+ window.clearTimeout(syncPageTimeoutRef.current);
98
+ }
99
+ };
100
+ }, []);
101
+ useLayoutEffect(() => {
102
+ if (typeof window === 'undefined') {
103
+ return;
104
+ }
105
+ const pageSelect = readPageSelect();
106
+ if (!pageSelect) {
107
+ return;
108
+ }
109
+ const currentPage = Number(pageSelect.value);
110
+ const currentPageSize = Number(readPageSizeSelect()?.value);
111
+ const pageSize = Number.isFinite(currentPageSize) && currentPageSize > 0
112
+ ? currentPageSize
113
+ : DEFAULT_PAGE_SIZE;
114
+ const maxPage = Math.max(1, Math.ceil(stableClusters.length / pageSize));
115
+ const nextPage = Math.min(selectedPageRef.current, maxPage);
116
+ selectedPageRef.current = nextPage;
117
+ if (nextPage <= 1 || currentPage === nextPage) {
118
+ return;
119
+ }
120
+ pageSelect.value = String(nextPage);
121
+ pageSelect.dispatchEvent(new Event('change', { bubbles: true }));
122
+ syncSelectedPageFromDom();
123
+ }, [
124
+ readPageSelect,
125
+ readPageSizeSelect,
126
+ stableClusters,
127
+ syncSelectedPageFromDom,
128
+ ]);
23
129
  const headerData = [
24
130
  {
25
131
  label: translations.headers.name,
@@ -208,7 +314,7 @@ export const C4ClusterList = ({ clusters, loading, urls, permissions, onNavigate
208
314
  paddingBlockEnd: 'var(--cds-spacing-07)',
209
315
  }, children: [_jsx("p", { style: { color: 'var(--cds-text-secondary)' }, children: translations.emptyState.noClusters }), permissions.canCreateCluster && (_jsx("div", { style: { marginTop: 'var(--cds-spacing-05)' }, children: _jsx(Button, { kind: 'primary', onClick: () => onNavigate(urls.createCluster), disabled: permissions.createClusterButtonDisabled, children: translations.createButton }) }))] })));
210
316
  }
211
- return (_jsx(C3DataTable, { headers: headerData, isLoading: loading, toolbar: toolbar, actions: actions, data: clusters, queryPrefix: 'cluster-list' }));
317
+ return (_jsx("div", { ref: tableContainerRef, children: _jsx(C3DataTable, { headers: headerData, isLoading: loading, toolbar: toolbar, actions: actions, data: stableClusters, queryPrefix: 'cluster-list' }) }));
212
318
  };
213
319
  return (_jsxs(_Fragment, { children: [trialNotification, _jsxs(C4PageTemplate, { loading: loading, header: {
214
320
  title: translations.title,
@@ -0,0 +1,132 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
4
+ * under one or more contributor license agreements. Licensed under a commercial license.
5
+ * You may not use this file except in compliance with the commercial license.
6
+ */
7
+ import { render, waitFor } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { C4ClusterList } from './c4-cluster-list.js';
10
+ function createCluster(id) {
11
+ return {
12
+ id: `cluster-${id}`,
13
+ name: `Cluster ${id}`,
14
+ generation: '8.8',
15
+ type: 'Production',
16
+ region: 'eu-central-1',
17
+ status: 'healthy',
18
+ tags: [],
19
+ originalIndex: id - 1,
20
+ };
21
+ }
22
+ function buildProps(clusters) {
23
+ return {
24
+ orgId: 'org-1',
25
+ clusters,
26
+ loading: false,
27
+ urls: {
28
+ clusterDetails: (clusterId) => `/org/org-1/clusters/${clusterId}`,
29
+ createCluster: '/org/org-1/clusters/create',
30
+ },
31
+ permissions: {
32
+ canCreateCluster: true,
33
+ canEditCluster: true,
34
+ canDeleteCluster: true,
35
+ canUpdateTag: true,
36
+ canSeeClusterLoad: false,
37
+ canWakeCluster: true,
38
+ createClusterButtonDisabled: false,
39
+ },
40
+ onNavigate: vi.fn(),
41
+ onDeleteCluster: vi.fn(),
42
+ onAssignTag: vi.fn(),
43
+ onWakeCluster: vi.fn(),
44
+ translations: {
45
+ title: 'Clusters',
46
+ createButton: 'Create cluster',
47
+ searchLabel: 'Search',
48
+ emptyState: {
49
+ noClusters: 'No clusters',
50
+ },
51
+ headers: {
52
+ name: 'Name',
53
+ generation: 'Generation',
54
+ type: 'Type',
55
+ region: 'Region',
56
+ status: 'Status',
57
+ tags: 'Tags',
58
+ },
59
+ overflowMenu: {
60
+ delete: 'Delete',
61
+ resume: 'Resume',
62
+ },
63
+ tags: {
64
+ addLabel: 'Add tag',
65
+ },
66
+ tooltips: {
67
+ clusterLoad: 'Cluster load',
68
+ },
69
+ },
70
+ isClusterPaused: () => false,
71
+ isRunningOnUnsupportedGeneration: () => false,
72
+ };
73
+ }
74
+ function getPageSelect() {
75
+ const pageSelect = document.querySelector('.cds--select__page-number select');
76
+ if (!pageSelect) {
77
+ throw new Error('Expected cluster list pagination page select');
78
+ }
79
+ return pageSelect;
80
+ }
81
+ describe('C4ClusterList pagination', () => {
82
+ it('preserves the selected page when clusters are added or removed', async () => {
83
+ const user = userEvent.setup();
84
+ const initialClusters = Array.from({ length: 20 }, (_, index) => {
85
+ return createCluster(index + 1);
86
+ });
87
+ const { rerender } = render(_jsx(C4ClusterList, { ...buildProps(initialClusters) }));
88
+ await waitFor(() => {
89
+ expect(getPageSelect().value).toBe('1');
90
+ });
91
+ await user.selectOptions(getPageSelect(), '2');
92
+ await waitFor(() => {
93
+ expect(getPageSelect().value).toBe('2');
94
+ });
95
+ rerender(_jsx(C4ClusterList, { ...buildProps([...initialClusters, createCluster(21)]) }));
96
+ await waitFor(() => {
97
+ expect(getPageSelect().value).toBe('2');
98
+ });
99
+ rerender(_jsx(C4ClusterList, { ...buildProps(initialClusters.slice(0, 19)) }));
100
+ await waitFor(() => {
101
+ expect(getPageSelect().value).toBe('2');
102
+ });
103
+ });
104
+ it('preserves the selected page when cluster data updates without changing list length', async () => {
105
+ const user = userEvent.setup();
106
+ const initialClusters = Array.from({ length: 20 }, (_, index) => {
107
+ return createCluster(index + 1);
108
+ });
109
+ const { rerender } = render(_jsx(C4ClusterList, { ...buildProps(initialClusters) }));
110
+ await waitFor(() => {
111
+ expect(getPageSelect().value).toBe('1');
112
+ });
113
+ await user.selectOptions(getPageSelect(), '2');
114
+ await waitFor(() => {
115
+ expect(getPageSelect().value).toBe('2');
116
+ });
117
+ const updatedClusters = initialClusters.map((cluster, index) => {
118
+ if (index !== 0) {
119
+ return cluster;
120
+ }
121
+ return {
122
+ ...cluster,
123
+ status: 'updating',
124
+ generation: '8.8.1',
125
+ };
126
+ });
127
+ rerender(_jsx(C4ClusterList, { ...buildProps(updatedClusters) }));
128
+ await waitFor(() => {
129
+ expect(getPageSelect().value).toBe('2');
130
+ });
131
+ });
132
+ });
@@ -90,7 +90,7 @@ export interface ClusterDetailsProps {
90
90
  }
91
91
  export declare const ClusterDetails: (props: ClusterDetailsProps) => JSX.Element;
92
92
  export interface ClusterDetailsGenerationUpdateProps {
93
- updateType: 'update-available' | 'update-available-wait-needed' | 'update-available-no-permissions' | 'no-update-latest' | 'no-update-refresh' | 'updating' | 'creating';
93
+ updateType: 'update-available' | 'update-available-wait-needed' | 'update-available-no-permissions' | 'no-update-latest' | 'updating' | 'creating';
94
94
  updateStates: {
95
95
  updateAvailable: {
96
96
  action: () => void;
@@ -107,10 +107,6 @@ export interface ClusterDetailsGenerationUpdateProps {
107
107
  noUpdateLatest: {
108
108
  label: string;
109
109
  };
110
- noUpdateRefresh: {
111
- action: () => void;
112
- label: string;
113
- };
114
110
  };
115
111
  }
116
112
  export declare const ClusterDetailsGenerationUpdate: (props: ClusterDetailsGenerationUpdateProps) => JSX.Element;
@@ -5,8 +5,8 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
5
5
  * You may not use this file except in compliance with the commercial license.
6
6
  */
7
7
  import { C3ClusterTag, } from '@camunda/camunda-composite-components';
8
- import { Button, CodeSnippet, IconButton, Layer, StructuredListBody, StructuredListCell, StructuredListRow, StructuredListWrapper, TextArea, } from '@carbon/react';
9
- import { Add, Information, Pause, Play, Renew, UpdateNow, } from '@carbon/react/icons/index.esm.js';
8
+ import { Button, CodeSnippet, Layer, StructuredListBody, StructuredListCell, StructuredListRow, StructuredListWrapper, TextArea, } from '@carbon/react';
9
+ import { Add, Information, Pause, Play, UpdateNow } from '@carbon/react/icons/index.esm.js';
10
10
  import { useEffect, useRef, useState } from 'react';
11
11
  import { C4ActionableNotification } from '../c4-actionable-notification/c4-actionable-notification.js';
12
12
  import { C4LinkButton } from '../c4-link-button/c4-link-button.js';
@@ -102,6 +102,7 @@ export const ClusterDetails = (props) => {
102
102
  display: 'grid',
103
103
  gridTemplateColumns: 'auto 1fr',
104
104
  gap: '0.5rem',
105
+ alignItems: 'center',
105
106
  }, children: [_jsx("p", { children: props.generation.value }), props.status?.value.toLowerCase() === 'healthy' && (_jsx(ClusterDetailsGenerationUpdate, { ...props.generation.update }))] }) })] }), props.type && (_jsxs(StructuredListRow, { style: rowStyle, children: [_jsx(StructuredListCell, { noWrap: true, children: props.type.label }), _jsx(StructuredListCell, { noWrap: true, children: _jsxs("div", { style: {
106
107
  display: 'inline-grid',
107
108
  gridAutoFlow: 'column',
@@ -119,7 +120,7 @@ export const ClusterDetailsGenerationUpdate = (props) => {
119
120
  const latestMessage = 'No updates available - running on latest available version';
120
121
  switch (props.updateType) {
121
122
  case 'update-available':
122
- component = (_jsx(Button, { onClick: () => props.updateStates.updateAvailable.action(), size: 'sm', renderIcon: UpdateNow, style: { top: '-0.4rem' }, children: props.updateStates.updateAvailable.label }));
123
+ component = (_jsx(Button, { onClick: () => props.updateStates.updateAvailable.action(), size: 'sm', renderIcon: UpdateNow, children: props.updateStates.updateAvailable.label }));
123
124
  break;
124
125
  case 'update-available-no-permissions':
125
126
  component = (_jsxs("div", { style: {
@@ -153,14 +154,6 @@ export const ClusterDetailsGenerationUpdate = (props) => {
153
154
  }, children: [_jsx(Information, {}), _jsx("p", { style: { wordBreak: 'break-word', overflowWrap: 'break-word' }, children: resolvedNoUpdateLatestLabel })] }));
154
155
  break;
155
156
  }
156
- case 'no-update-refresh':
157
- component = (_jsxs("div", { style: {
158
- display: 'grid',
159
- gridTemplateColumns: '16px auto 1fr',
160
- gap: '0.5rem',
161
- alignItems: 'start',
162
- }, children: [_jsx(Information, {}), _jsx("p", { style: { wordBreak: 'break-word', overflowWrap: 'break-word' }, children: props.updateStates.noUpdateRefresh.label }), _jsx("div", { children: _jsx(IconButton, { label: 'Refresh', onClick: () => props.updateStates.noUpdateRefresh.action(), kind: 'ghost', style: { top: '-0.2rem' }, size: 'xs', children: _jsx(Renew, {}) }) })] }));
163
- break;
164
157
  case 'creating':
165
158
  case 'updating':
166
159
  component = _jsx(_Fragment, {});