@terasky/backstage-plugin-vcf-automation 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,20 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useMemo } from 'react';
3
- import { useEntity } from '@backstage/plugin-catalog-react';
2
+ import { useState, useMemo, useCallback } from 'react';
3
+ import { useEntity, catalogApiRef } from '@backstage/plugin-catalog-react';
4
4
  import { useApi } from '@backstage/core-plugin-api';
5
+ import { usePermission } from '@backstage/plugin-permission-react';
5
6
  import { vcfAutomationApiRef } from '../api/VcfAutomationClient.esm.js';
7
+ import { supervisorResourceEditPermission } from '@terasky/backstage-plugin-vcf-automation-common';
8
+ import Editor from '@monaco-editor/react';
6
9
  import { InfoCard, Progress, ResponseErrorPanel, StructuredMetadataTable, CodeSnippet, StatusPending, StatusError, StatusOK } from '@backstage/core-components';
7
- import { Typography, Grid, Box, Chip, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core';
10
+ import { Typography, Grid, Box, Chip, Button, Accordion, AccordionSummary, AccordionDetails, Dialog, DialogTitle, DialogContent, DialogActions, Snackbar } from '@material-ui/core';
11
+ import { Alert } from '@material-ui/lab';
8
12
  import { makeStyles } from '@material-ui/core/styles';
9
13
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10
- import yaml from 'js-yaml';
14
+ import EditIcon from '@material-ui/icons/Edit';
15
+ import yaml__default from 'js-yaml';
11
16
  import useAsync from 'react-use/lib/useAsync';
17
+ import { VCFAutomationVMPowerManagement } from './VCFAutomationVMPowerManagement.esm.js';
12
18
 
13
19
  const useStyles = makeStyles((theme) => ({
14
20
  statusChip: {
@@ -29,15 +35,67 @@ const useStyles = makeStyles((theme) => ({
29
35
  dependencyChip: {
30
36
  margin: theme.spacing(0.25),
31
37
  cursor: "pointer"
38
+ },
39
+ editButton: {
40
+ marginTop: theme.spacing(1)
41
+ },
42
+ monacoEditor: {
43
+ flex: 1,
44
+ minHeight: "500px",
45
+ border: `1px solid ${theme.palette.divider}`,
46
+ borderRadius: theme.shape.borderRadius
47
+ },
48
+ validationStatus: {
49
+ padding: theme.spacing(1),
50
+ borderTop: `1px solid ${theme.palette.divider}`,
51
+ backgroundColor: theme.palette.background.paper,
52
+ flexShrink: 0
53
+ },
54
+ dialogContent: {
55
+ padding: theme.spacing(2),
56
+ height: "100%",
57
+ display: "flex",
58
+ flexDirection: "column",
59
+ overflow: "hidden"
60
+ },
61
+ editorContainer: {
62
+ flex: 1,
63
+ display: "flex",
64
+ flexDirection: "column",
65
+ minHeight: 0,
66
+ overflow: "hidden"
67
+ },
68
+ yamlValidationError: {
69
+ color: theme.palette.error.main,
70
+ marginTop: theme.spacing(1),
71
+ fontSize: "0.875rem"
32
72
  }
33
73
  }));
34
74
  const VCFAutomationCCIResourceOverview = () => {
35
75
  const classes = useStyles();
36
76
  const { entity } = useEntity();
37
77
  const api = useApi(vcfAutomationApiRef);
78
+ const catalogApi = useApi(catalogApiRef);
79
+ const { allowed: canEditResource } = usePermission({
80
+ permission: supervisorResourceEditPermission
81
+ });
82
+ const [editModalOpen, setEditModalOpen] = useState(false);
83
+ const [editingYaml, setEditingYaml] = useState("");
84
+ const [originalManifest, setOriginalManifest] = useState(null);
85
+ const [isLoadingManifest, setIsLoadingManifest] = useState(false);
86
+ const [isSaving, setIsSaving] = useState(false);
87
+ const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
88
+ const [yamlValidationError, setYamlValidationError] = useState("");
89
+ const [snackbar, setSnackbar] = useState({
90
+ open: false,
91
+ message: "",
92
+ severity: "success"
93
+ });
38
94
  const deploymentId = entity.spec?.system;
39
95
  const resourceId = entity.metadata.name;
40
96
  const instanceName = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-instance"];
97
+ const isStandalone = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-origin"] === "STANDALONE";
98
+ const vmOrganizationType = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-version"] === "9" && entity.metadata.tags?.some((tag) => tag.startsWith("vcf-automation:")) ? "all-apps" : "vm-apps";
41
99
  const annotationData = useMemo(() => {
42
100
  const resourceProperties = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-properties"];
43
101
  const resourceManifest = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-manifest"];
@@ -57,32 +115,214 @@ const VCFAutomationCCIResourceOverview = () => {
57
115
  ]);
58
116
  const needsApiCall = !annotationData.resourceData || !annotationData.manifest || !annotationData.objectData;
59
117
  const { value: apiResourceData, loading, error } = useAsync(async () => {
60
- if (!needsApiCall || !deploymentId || !resourceId) {
118
+ if (!needsApiCall || !resourceId) {
61
119
  return null;
62
120
  }
63
121
  try {
64
- const response = await api.getDeploymentResources(deploymentId, instanceName);
65
- let resources = null;
66
- if (response) {
67
- if (Array.isArray(response)) {
68
- resources = response;
69
- } else if (response.content && Array.isArray(response.content)) {
70
- resources = response.content;
122
+ if (isStandalone) {
123
+ const response = await api.getSupervisorResource(resourceId, instanceName);
124
+ if (response) {
125
+ return {
126
+ id: response.id,
127
+ properties: {
128
+ manifest: {
129
+ apiVersion: response.apiVersion,
130
+ kind: response.kind,
131
+ metadata: response.metadata,
132
+ spec: response.spec
133
+ },
134
+ object: {
135
+ apiVersion: response.apiVersion,
136
+ kind: response.kind,
137
+ metadata: response.metadata,
138
+ spec: response.spec,
139
+ status: response.status
140
+ },
141
+ context: JSON.stringify({
142
+ namespace: response.metadata.namespace,
143
+ apiVersion: response.apiVersion,
144
+ kind: response.kind,
145
+ standalone: true
146
+ })
147
+ }
148
+ };
149
+ }
150
+ } else {
151
+ if (!deploymentId) {
152
+ return null;
153
+ }
154
+ const response = await api.getDeploymentResources(deploymentId, instanceName);
155
+ let resources = null;
156
+ if (response) {
157
+ if (Array.isArray(response)) {
158
+ resources = response;
159
+ } else if (response.content && Array.isArray(response.content)) {
160
+ resources = response.content;
161
+ }
162
+ }
163
+ if (resources) {
164
+ return resources.find((r) => r.id === resourceId);
71
165
  }
72
- }
73
- if (resources) {
74
- return resources.find((r) => r.id === resourceId);
75
166
  }
76
167
  return null;
77
168
  } catch (apiError) {
78
169
  console.error("Failed to fetch resource data:", apiError);
79
170
  return null;
80
171
  }
81
- }, [needsApiCall, deploymentId, resourceId, instanceName]);
172
+ }, [needsApiCall, isStandalone, deploymentId, resourceId, instanceName]);
82
173
  const resourceData = annotationData.resourceData || apiResourceData;
83
174
  const manifest = annotationData.manifest || apiResourceData?.properties?.manifest;
84
175
  const objectData = annotationData.objectData || apiResourceData?.properties?.object;
85
176
  const resourceContext = annotationData.resourceContext || apiResourceData?.properties?.context;
177
+ const resourceKind = manifest?.kind || objectData?.kind;
178
+ const resourceName = manifest?.metadata?.name || objectData?.metadata?.name;
179
+ const namespaceName = manifest?.metadata?.namespace || objectData?.metadata?.namespace;
180
+ const apiVersion = manifest?.apiVersion || objectData?.apiVersion;
181
+ const vmName = resourceName;
182
+ const extractNamespaceUrnId = useCallback((endpoint) => {
183
+ const match = endpoint.match(/\/namespaces\/(urn:vcloud:namespace:[^\/]+)/);
184
+ return match ? match[1] : void 0;
185
+ }, []);
186
+ const findCCINamespaceParent = useCallback(async (currentEntity, depth = 0) => {
187
+ if (depth >= 3) return void 0;
188
+ if (currentEntity.spec?.type === "CCI.Supervisor.Namespace") {
189
+ const endpoint = currentEntity.metadata?.annotations?.["terasky.backstage.io/vcf-automation-cci-namespace-endpoint"];
190
+ if (endpoint) {
191
+ return extractNamespaceUrnId(endpoint);
192
+ }
193
+ }
194
+ const parentRef = currentEntity.spec?.subcomponentOf;
195
+ if (!parentRef) return void 0;
196
+ try {
197
+ const parentEntity = await catalogApi.getEntityByRef(parentRef);
198
+ if (parentEntity) {
199
+ return await findCCINamespaceParent(parentEntity, depth + 1);
200
+ }
201
+ } catch (error2) {
202
+ console.warn("Failed to fetch parent entity:", parentRef, error2);
203
+ }
204
+ return void 0;
205
+ }, [catalogApi, extractNamespaceUrnId]);
206
+ const { value: namespaceUrnId } = useAsync(async () => {
207
+ if (!namespaceName) return void 0;
208
+ if (isStandalone) {
209
+ let contextData = null;
210
+ try {
211
+ contextData = typeof resourceContext === "string" ? JSON.parse(resourceContext || "{}") : resourceContext;
212
+ } catch (error2) {
213
+ console.log("Debug - Resource context is not JSON, treating as string:", resourceContext);
214
+ contextData = null;
215
+ }
216
+ const urnId = contextData?.namespaceUrnId || namespaceName;
217
+ console.log("Debug - Standalone CCI Resource URN ID resolution:", {
218
+ namespaceName,
219
+ resourceContext,
220
+ contextData,
221
+ urnId
222
+ });
223
+ return urnId;
224
+ } else {
225
+ console.log("Debug - Looking for CCI Supervisor Namespace parent for deployment-managed resource");
226
+ const urnId = await findCCINamespaceParent(entity);
227
+ console.log("Debug - Deployment-managed CCI Resource URN ID resolution:", {
228
+ namespaceName,
229
+ urnId,
230
+ entityRef: entity.metadata.name
231
+ });
232
+ return urnId || namespaceName;
233
+ }
234
+ }, [namespaceName, resourceContext, isStandalone, entity, findCCINamespaceParent]);
235
+ const handleEditResource = useCallback(async () => {
236
+ if (!canEditResource || !namespaceName || !resourceName || !namespaceUrnId || !apiVersion || !resourceKind) {
237
+ return;
238
+ }
239
+ setIsLoadingManifest(true);
240
+ setEditModalOpen(true);
241
+ try {
242
+ const manifestResponse = await api.getSupervisorResourceManifest(
243
+ namespaceUrnId,
244
+ namespaceName,
245
+ resourceName,
246
+ apiVersion,
247
+ resourceKind,
248
+ instanceName
249
+ );
250
+ setOriginalManifest(manifestResponse);
251
+ const yamlContent = yaml__default.dump(manifestResponse, {
252
+ indent: 2,
253
+ lineWidth: -1,
254
+ noRefs: true,
255
+ sortKeys: false
256
+ });
257
+ setEditingYaml(yamlContent);
258
+ setYamlValidationError("");
259
+ } catch (error2) {
260
+ setSnackbar({
261
+ open: true,
262
+ message: `Failed to fetch resource manifest: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
263
+ severity: "error"
264
+ });
265
+ setEditModalOpen(false);
266
+ } finally {
267
+ setIsLoadingManifest(false);
268
+ }
269
+ }, [canEditResource, namespaceName, resourceName, namespaceUrnId, apiVersion, resourceKind, instanceName, api]);
270
+ const handleSaveResource = useCallback(async () => {
271
+ if (!originalManifest || !namespaceName || !resourceName || !namespaceUrnId || !apiVersion || !resourceKind) {
272
+ return;
273
+ }
274
+ setIsSaving(true);
275
+ setConfirmDialogOpen(false);
276
+ try {
277
+ const updatedManifest = yaml__default.load(editingYaml);
278
+ await api.updateSupervisorResourceManifest(
279
+ namespaceUrnId,
280
+ namespaceName,
281
+ resourceName,
282
+ apiVersion,
283
+ resourceKind,
284
+ updatedManifest,
285
+ instanceName
286
+ );
287
+ setSnackbar({
288
+ open: true,
289
+ message: "Resource manifest updated successfully",
290
+ severity: "success"
291
+ });
292
+ setEditModalOpen(false);
293
+ setTimeout(() => window.location.reload(), 1e3);
294
+ } catch (error2) {
295
+ setSnackbar({
296
+ open: true,
297
+ message: `Failed to update resource manifest: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
298
+ severity: "error"
299
+ });
300
+ } finally {
301
+ setIsSaving(false);
302
+ }
303
+ }, [originalManifest, namespaceName, resourceName, namespaceUrnId, apiVersion, resourceKind, editingYaml, instanceName, api]);
304
+ const handleCloseSnackbar = useCallback(() => {
305
+ setSnackbar((prev) => ({ ...prev, open: false }));
306
+ }, []);
307
+ const validateYaml = useCallback((yamlString) => {
308
+ try {
309
+ yaml__default.load(yamlString);
310
+ setYamlValidationError("");
311
+ return true;
312
+ } catch (error2) {
313
+ const errorMessage = error2 instanceof Error ? error2.message : "Invalid YAML syntax";
314
+ setYamlValidationError(errorMessage);
315
+ return false;
316
+ }
317
+ }, []);
318
+ const handleYamlChange = useCallback((value) => {
319
+ setEditingYaml(value);
320
+ if (value.trim()) {
321
+ validateYaml(value);
322
+ } else {
323
+ setYamlValidationError("");
324
+ }
325
+ }, [validateYaml]);
86
326
  if (loading) {
87
327
  return /* @__PURE__ */ jsx(InfoCard, { title: "CCI Supervisor Resource", children: /* @__PURE__ */ jsx(Progress, {}) });
88
328
  }
@@ -123,7 +363,7 @@ const VCFAutomationCCIResourceOverview = () => {
123
363
  const formatYaml = (data) => {
124
364
  try {
125
365
  const reorderedData = reorderKubernetesResource(data);
126
- return yaml.dump(reorderedData, {
366
+ return yaml__default.dump(reorderedData, {
127
367
  indent: 2,
128
368
  lineWidth: -1,
129
369
  noRefs: true,
@@ -156,109 +396,269 @@ const VCFAutomationCCIResourceOverview = () => {
156
396
  return Object.keys(statusInfo).length > 0 ? statusInfo : null;
157
397
  };
158
398
  const objectStatus = getObjectStatus();
159
- return /* @__PURE__ */ jsx(InfoCard, { title: "CCI Supervisor Resource Overview", children: /* @__PURE__ */ jsxs(Grid, { container: true, spacing: 3, children: [
160
- /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
161
- /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Basic Information" }),
162
- /* @__PURE__ */ jsx(StructuredMetadataTable, { metadata: basicInfo })
163
- ] }),
164
- entity.spec?.dependsOn && Array.isArray(entity.spec.dependsOn) && entity.spec.dependsOn.length > 0 && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
165
- /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Dependencies" }),
166
- /* @__PURE__ */ jsx(Box, { children: entity.spec.dependsOn.filter((dep) => typeof dep === "string").map((dep, index) => /* @__PURE__ */ jsx(
399
+ return /* @__PURE__ */ jsxs(InfoCard, { title: "CCI Supervisor Resource Overview", children: [
400
+ /* @__PURE__ */ jsxs(Grid, { container: true, spacing: 3, children: [
401
+ isStandalone && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(Box, { mb: 2, children: /* @__PURE__ */ jsx(
167
402
  Chip,
168
403
  {
169
- label: dep,
170
- size: "small",
171
- className: classes.dependencyChip,
172
- color: "primary",
173
- variant: "outlined"
174
- },
175
- index
176
- )) })
177
- ] }),
178
- resourceData.wait?.conditions && resourceData.wait.conditions.length > 0 && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
179
- /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Wait Conditions" }),
180
- /* @__PURE__ */ jsx(Box, { children: resourceData.wait.conditions.map((condition, index) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", mb: 1, children: [
181
- renderStatusIcon(condition.status),
404
+ label: "Standalone Resource",
405
+ color: "secondary",
406
+ variant: "outlined",
407
+ size: "small"
408
+ }
409
+ ) }) }),
410
+ /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
411
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Basic Information" }),
412
+ /* @__PURE__ */ jsx(StructuredMetadataTable, { metadata: basicInfo })
413
+ ] }),
414
+ vmOrganizationType === "all-apps" && resourceKind === "VirtualMachine" && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
415
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Power Management" }),
182
416
  /* @__PURE__ */ jsx(
183
- Chip,
417
+ VCFAutomationVMPowerManagement,
184
418
  {
185
- label: `${condition.type}: ${condition.status}`,
186
- size: "small",
187
- className: classes.conditionChip,
188
- color: condition.status === "True" ? "primary" : "default"
419
+ entity,
420
+ resourceId,
421
+ instanceName,
422
+ isStandalone,
423
+ vmName: isStandalone ? vmName : void 0,
424
+ namespaceName: isStandalone ? namespaceName : void 0,
425
+ namespaceUrnId: isStandalone ? namespaceUrnId : void 0
189
426
  }
190
427
  )
191
- ] }, index)) })
192
- ] }),
193
- objectData?.status?.conditions && objectData.status.conditions.length > 0 && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
194
- /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Resource Conditions" }),
195
- /* @__PURE__ */ jsx(Box, { children: objectData.status.conditions.map((condition, index) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", mb: 1, children: [
196
- renderStatusIcon(condition.status),
428
+ ] }),
429
+ canEditResource && vmOrganizationType === "all-apps" && resourceKind === "VirtualMachine" && resourceName && namespaceName && namespaceUrnId && apiVersion && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsxs(Box, { mt: vmOrganizationType === "all-apps" ? 0 : 2, children: [
430
+ vmOrganizationType !== "all-apps" && /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Resource Management" }),
431
+ /* @__PURE__ */ jsx(
432
+ Button,
433
+ {
434
+ variant: "outlined",
435
+ color: "primary",
436
+ startIcon: /* @__PURE__ */ jsx(EditIcon, {}),
437
+ onClick: handleEditResource,
438
+ className: classes.editButton,
439
+ disabled: isLoadingManifest,
440
+ children: "Edit Resource Manifest"
441
+ }
442
+ )
443
+ ] }) }),
444
+ canEditResource && (resourceKind !== "VirtualMachine" || vmOrganizationType !== "all-apps") && resourceName && namespaceName && namespaceUrnId && apiVersion && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
445
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Resource Management" }),
197
446
  /* @__PURE__ */ jsx(
447
+ Button,
448
+ {
449
+ variant: "outlined",
450
+ color: "primary",
451
+ startIcon: /* @__PURE__ */ jsx(EditIcon, {}),
452
+ onClick: handleEditResource,
453
+ className: classes.editButton,
454
+ disabled: isLoadingManifest,
455
+ children: "Edit Resource Manifest"
456
+ }
457
+ )
458
+ ] }),
459
+ entity.spec?.dependsOn && Array.isArray(entity.spec.dependsOn) && entity.spec.dependsOn.length > 0 && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
460
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Dependencies" }),
461
+ /* @__PURE__ */ jsx(Box, { children: entity.spec.dependsOn.filter((dep) => typeof dep === "string").map((dep, index) => /* @__PURE__ */ jsx(
198
462
  Chip,
199
463
  {
200
- label: `${condition.type}: ${condition.status}`,
464
+ label: dep,
201
465
  size: "small",
202
- className: classes.conditionChip,
203
- color: condition.status === "True" ? "primary" : "default"
466
+ className: classes.dependencyChip,
467
+ color: "primary",
468
+ variant: "outlined"
469
+ },
470
+ index
471
+ )) })
472
+ ] }),
473
+ resourceData.wait?.conditions && resourceData.wait.conditions.length > 0 && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
474
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Wait Conditions" }),
475
+ /* @__PURE__ */ jsx(Box, { children: resourceData.wait.conditions.map((condition, index) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", mb: 1, children: [
476
+ renderStatusIcon(condition.status),
477
+ /* @__PURE__ */ jsx(
478
+ Chip,
479
+ {
480
+ label: `${condition.type}: ${condition.status}`,
481
+ size: "small",
482
+ className: classes.conditionChip,
483
+ color: condition.status === "True" ? "primary" : "default"
484
+ }
485
+ )
486
+ ] }, index)) })
487
+ ] }),
488
+ objectData?.status?.conditions && objectData.status.conditions.length > 0 && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
489
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Resource Conditions" }),
490
+ /* @__PURE__ */ jsx(Box, { children: objectData.status.conditions.map((condition, index) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", mb: 1, children: [
491
+ renderStatusIcon(condition.status),
492
+ /* @__PURE__ */ jsx(
493
+ Chip,
494
+ {
495
+ label: `${condition.type}: ${condition.status}`,
496
+ size: "small",
497
+ className: classes.conditionChip,
498
+ color: condition.status === "True" ? "primary" : "default"
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", style: { marginLeft: 8 }, children: condition.lastTransitionTime })
502
+ ] }, index)) })
503
+ ] }),
504
+ objectStatus && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
505
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Resource Status" }),
506
+ /* @__PURE__ */ jsx(StructuredMetadataTable, { metadata: objectStatus })
507
+ ] }),
508
+ manifest && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsxs(Accordion, { className: classes.yamlContainer, children: [
509
+ /* @__PURE__ */ jsx(
510
+ AccordionSummary,
511
+ {
512
+ expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}),
513
+ "aria-controls": "manifest-content",
514
+ id: "manifest-header",
515
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Resource Manifest" })
204
516
  }
205
517
  ),
206
- /* @__PURE__ */ jsx(Typography, { variant: "caption", style: { marginLeft: 8 }, children: condition.lastTransitionTime })
207
- ] }, index)) })
208
- ] }),
209
- objectStatus && /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
210
- /* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Resource Status" }),
211
- /* @__PURE__ */ jsx(StructuredMetadataTable, { metadata: objectStatus })
212
- ] }),
213
- manifest && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsxs(Accordion, { className: classes.yamlContainer, children: [
214
- /* @__PURE__ */ jsx(
215
- AccordionSummary,
216
- {
217
- expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}),
218
- "aria-controls": "manifest-content",
219
- id: "manifest-header",
220
- children: /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Resource Manifest" })
221
- }
222
- ),
223
- /* @__PURE__ */ jsx(AccordionDetails, { children: /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
224
- CodeSnippet,
225
- {
226
- text: formatYaml(manifest),
227
- language: "yaml",
228
- showLineNumbers: true,
229
- customStyle: {
230
- fontSize: "12px",
231
- maxHeight: "500px",
232
- overflow: "auto"
518
+ /* @__PURE__ */ jsx(AccordionDetails, { children: /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
519
+ CodeSnippet,
520
+ {
521
+ text: formatYaml(manifest),
522
+ language: "yaml",
523
+ showLineNumbers: true,
524
+ customStyle: {
525
+ fontSize: "12px",
526
+ maxHeight: "500px",
527
+ overflow: "auto"
528
+ }
233
529
  }
234
- }
235
- ) }) })
236
- ] }) }),
237
- objectData && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsxs(Accordion, { className: classes.yamlContainer, children: [
238
- /* @__PURE__ */ jsx(
239
- AccordionSummary,
240
- {
241
- expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}),
242
- "aria-controls": "object-content",
243
- id: "object-header",
244
- children: /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Kubernetes Object" })
245
- }
246
- ),
247
- /* @__PURE__ */ jsx(AccordionDetails, { children: /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
248
- CodeSnippet,
249
- {
250
- text: formatYaml(objectData),
251
- language: "yaml",
252
- showLineNumbers: true,
253
- customStyle: {
254
- fontSize: "12px",
255
- maxHeight: "500px",
256
- overflow: "auto"
530
+ ) }) })
531
+ ] }) }),
532
+ objectData && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsxs(Accordion, { className: classes.yamlContainer, children: [
533
+ /* @__PURE__ */ jsx(
534
+ AccordionSummary,
535
+ {
536
+ expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}),
537
+ "aria-controls": "object-content",
538
+ id: "object-header",
539
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Kubernetes Object" })
257
540
  }
258
- }
259
- ) }) })
260
- ] }) })
261
- ] }) });
541
+ ),
542
+ /* @__PURE__ */ jsx(AccordionDetails, { children: /* @__PURE__ */ jsx(Box, { width: "100%", children: /* @__PURE__ */ jsx(
543
+ CodeSnippet,
544
+ {
545
+ text: formatYaml(objectData),
546
+ language: "yaml",
547
+ showLineNumbers: true,
548
+ customStyle: {
549
+ fontSize: "12px",
550
+ maxHeight: "500px",
551
+ overflow: "auto"
552
+ }
553
+ }
554
+ ) }) })
555
+ ] }) })
556
+ ] }),
557
+ /* @__PURE__ */ jsxs(
558
+ Dialog,
559
+ {
560
+ open: editModalOpen,
561
+ onClose: () => setEditModalOpen(false),
562
+ maxWidth: "xl",
563
+ fullWidth: true,
564
+ PaperProps: {
565
+ style: {
566
+ height: "90vh",
567
+ maxHeight: "90vh"
568
+ }
569
+ },
570
+ children: [
571
+ /* @__PURE__ */ jsxs(DialogTitle, { children: [
572
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Edit Resource Manifest" }),
573
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "textSecondary", children: [
574
+ resourceName,
575
+ " (",
576
+ resourceKind,
577
+ ")"
578
+ ] })
579
+ ] }),
580
+ /* @__PURE__ */ jsx(DialogContent, { className: classes.dialogContent, dividers: true, children: isLoadingManifest ? /* @__PURE__ */ jsx(Box, { display: "flex", justifyContent: "center", alignItems: "center", flex: 1, children: /* @__PURE__ */ jsx(Progress, {}) }) : /* @__PURE__ */ jsxs(Box, { className: classes.editorContainer, children: [
581
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "YAML Editor" }),
582
+ /* @__PURE__ */ jsx(Box, { className: classes.monacoEditor, children: /* @__PURE__ */ jsx(
583
+ Editor,
584
+ {
585
+ height: "100%",
586
+ defaultLanguage: "yaml",
587
+ value: editingYaml,
588
+ onChange: (value) => handleYamlChange(value || ""),
589
+ theme: "vs-dark",
590
+ options: {
591
+ minimap: { enabled: false },
592
+ scrollBeyondLastLine: false,
593
+ fontSize: 14,
594
+ lineNumbers: "on",
595
+ wordWrap: "off",
596
+ automaticLayout: true,
597
+ tabSize: 2,
598
+ insertSpaces: true,
599
+ folding: true,
600
+ renderWhitespace: "selection"
601
+ }
602
+ }
603
+ ) }),
604
+ /* @__PURE__ */ jsx(Box, { className: classes.validationStatus, children: yamlValidationError ? /* @__PURE__ */ jsxs(Typography, { className: classes.yamlValidationError, children: [
605
+ "\u26A0\uFE0F YAML Validation Error: ",
606
+ yamlValidationError
607
+ ] }) : editingYaml.trim() ? /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "textSecondary", children: "\u2705 YAML syntax is valid" }) : /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "textSecondary", children: "Enter YAML content above" }) })
608
+ ] }) }),
609
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
610
+ /* @__PURE__ */ jsx(Button, { onClick: () => setEditModalOpen(false), color: "primary", children: "Cancel" }),
611
+ /* @__PURE__ */ jsx(
612
+ Button,
613
+ {
614
+ onClick: () => setConfirmDialogOpen(true),
615
+ color: "primary",
616
+ variant: "contained",
617
+ disabled: isLoadingManifest || !editingYaml.trim() || !!yamlValidationError,
618
+ children: "Save Changes"
619
+ }
620
+ )
621
+ ] })
622
+ ]
623
+ }
624
+ ),
625
+ /* @__PURE__ */ jsxs(
626
+ Dialog,
627
+ {
628
+ open: confirmDialogOpen,
629
+ onClose: () => setConfirmDialogOpen(false),
630
+ maxWidth: "sm",
631
+ fullWidth: true,
632
+ children: [
633
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Confirm Changes" }),
634
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { children: "Are you sure you want to apply these changes to the resource? This action will update the Kubernetes resource based on your modifications." }) }),
635
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
636
+ /* @__PURE__ */ jsx(Button, { onClick: () => setConfirmDialogOpen(false), color: "primary", children: "Cancel" }),
637
+ /* @__PURE__ */ jsx(
638
+ Button,
639
+ {
640
+ onClick: handleSaveResource,
641
+ color: "primary",
642
+ variant: "contained",
643
+ disabled: isSaving,
644
+ children: isSaving ? "Applying..." : "Apply Changes"
645
+ }
646
+ )
647
+ ] })
648
+ ]
649
+ }
650
+ ),
651
+ /* @__PURE__ */ jsx(
652
+ Snackbar,
653
+ {
654
+ open: snackbar.open,
655
+ autoHideDuration: 6e3,
656
+ onClose: handleCloseSnackbar,
657
+ anchorOrigin: { vertical: "bottom", horizontal: "left" },
658
+ children: /* @__PURE__ */ jsx(Alert, { onClose: handleCloseSnackbar, severity: snackbar.severity, children: snackbar.message })
659
+ }
660
+ )
661
+ ] });
262
662
  };
263
663
 
264
664
  export { VCFAutomationCCIResourceOverview };