@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.
- package/dist/api/VcfAutomationClient.esm.js +148 -0
- package/dist/api/VcfAutomationClient.esm.js.map +1 -1
- package/dist/components/VCFAutomationCCINamespaceDetails.esm.js +16 -2
- package/dist/components/VCFAutomationCCINamespaceDetails.esm.js.map +1 -1
- package/dist/components/VCFAutomationCCINamespaceOverview.esm.js +35 -7
- package/dist/components/VCFAutomationCCINamespaceOverview.esm.js.map +1 -1
- package/dist/components/VCFAutomationCCIResourceDetails.esm.js +400 -19
- package/dist/components/VCFAutomationCCIResourceDetails.esm.js.map +1 -1
- package/dist/components/VCFAutomationCCIResourceOverview.esm.js +506 -106
- package/dist/components/VCFAutomationCCIResourceOverview.esm.js.map +1 -1
- package/dist/components/VCFAutomationVMPowerManagement.esm.js +262 -0
- package/dist/components/VCFAutomationVMPowerManagement.esm.js.map +1 -0
- package/dist/index.d.ts +83 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
3
|
+
import { useEntity, catalogApiRef } from '@backstage/plugin-catalog-react';
|
|
4
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
5
|
+
import { usePermission } from '@backstage/plugin-permission-react';
|
|
6
|
+
import { useAsync } from 'react-use';
|
|
7
|
+
import Editor from '@monaco-editor/react';
|
|
3
8
|
import { InfoCard, StructuredMetadataTable, Table, CodeSnippet, StatusPending, StatusError, StatusOK } from '@backstage/core-components';
|
|
4
|
-
import { Typography, Grid, Card, CardContent, Tabs, Tab, Box, Chip, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core';
|
|
9
|
+
import { Typography, Grid, Card, CardContent, Tabs, Tab, Box, Chip, Accordion, AccordionSummary, AccordionDetails, Button, Dialog, DialogTitle, DialogContent, DialogActions, Snackbar } from '@material-ui/core';
|
|
10
|
+
import { Alert } from '@material-ui/lab';
|
|
5
11
|
import { makeStyles } from '@material-ui/core/styles';
|
|
6
12
|
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
|
7
|
-
import yaml from 'js-yaml';
|
|
8
|
-
import {
|
|
13
|
+
import * as yaml from 'js-yaml';
|
|
14
|
+
import { vcfAutomationApiRef } from '../api/VcfAutomationClient.esm.js';
|
|
15
|
+
import { supervisorResourceEditPermission } from '@terasky/backstage-plugin-vcf-automation-common';
|
|
9
16
|
|
|
10
17
|
const useStyles = makeStyles((theme) => ({
|
|
11
18
|
statusChip: {
|
|
@@ -35,6 +42,33 @@ const useStyles = makeStyles((theme) => ({
|
|
|
35
42
|
},
|
|
36
43
|
tabPanel: {
|
|
37
44
|
paddingTop: theme.spacing(2)
|
|
45
|
+
},
|
|
46
|
+
yamlEditorContainer: {
|
|
47
|
+
height: "70vh",
|
|
48
|
+
minHeight: "500px",
|
|
49
|
+
display: "flex",
|
|
50
|
+
flexDirection: "column"
|
|
51
|
+
},
|
|
52
|
+
monacoEditor: {
|
|
53
|
+
flex: 1,
|
|
54
|
+
border: `1px solid ${theme.palette.divider}`,
|
|
55
|
+
borderRadius: theme.shape.borderRadius
|
|
56
|
+
},
|
|
57
|
+
validationStatus: {
|
|
58
|
+
padding: theme.spacing(1),
|
|
59
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
60
|
+
backgroundColor: theme.palette.background.paper,
|
|
61
|
+
flexShrink: 0
|
|
62
|
+
},
|
|
63
|
+
yamlValidationError: {
|
|
64
|
+
color: theme.palette.error.main,
|
|
65
|
+
fontSize: "0.875rem"
|
|
66
|
+
},
|
|
67
|
+
editorActions: {
|
|
68
|
+
display: "flex",
|
|
69
|
+
gap: theme.spacing(1),
|
|
70
|
+
marginTop: theme.spacing(2),
|
|
71
|
+
justifyContent: "flex-end"
|
|
38
72
|
}
|
|
39
73
|
}));
|
|
40
74
|
function TabPanel(props) {
|
|
@@ -55,21 +89,273 @@ const VCFAutomationCCIResourceDetails = () => {
|
|
|
55
89
|
const classes = useStyles();
|
|
56
90
|
const { entity } = useEntity();
|
|
57
91
|
const [tabValue, setTabValue] = useState(0);
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
92
|
+
const api = useApi(vcfAutomationApiRef);
|
|
93
|
+
const catalogApi = useApi(catalogApiRef);
|
|
94
|
+
const { allowed: canEditResource } = usePermission({
|
|
95
|
+
permission: supervisorResourceEditPermission
|
|
96
|
+
});
|
|
97
|
+
const [editingYaml, setEditingYaml] = useState("");
|
|
98
|
+
const [originalManifest, setOriginalManifest] = useState(null);
|
|
99
|
+
const [isLoadingManifest, setIsLoadingManifest] = useState(false);
|
|
100
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
101
|
+
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
|
102
|
+
const [yamlValidationError, setYamlValidationError] = useState("");
|
|
103
|
+
const [snackbar, setSnackbar] = useState({
|
|
104
|
+
open: false,
|
|
105
|
+
message: "",
|
|
106
|
+
severity: "success"
|
|
107
|
+
});
|
|
108
|
+
const deploymentId = entity.spec?.system;
|
|
109
|
+
const resourceId = entity.metadata.name;
|
|
110
|
+
const instanceName = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-instance"];
|
|
111
|
+
const isStandalone = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-origin"] === "STANDALONE";
|
|
112
|
+
const annotationData = useMemo(() => {
|
|
113
|
+
const resourceProperties = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-properties"];
|
|
114
|
+
const resourceManifest = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-manifest"];
|
|
115
|
+
const resourceObject = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-object"];
|
|
116
|
+
const resourceContext2 = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-context"];
|
|
117
|
+
const resourceState2 = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-state"];
|
|
118
|
+
const syncStatus2 = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-sync-status"];
|
|
119
|
+
const createdAt2 = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-created-at"];
|
|
120
|
+
const origin2 = entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-origin"];
|
|
121
|
+
return {
|
|
122
|
+
resourceData: resourceProperties && resourceProperties !== "{}" ? JSON.parse(resourceProperties) : null,
|
|
123
|
+
manifest: resourceManifest && resourceManifest !== "{}" ? JSON.parse(resourceManifest) : null,
|
|
124
|
+
objectData: resourceObject && resourceObject !== "{}" ? JSON.parse(resourceObject) : null,
|
|
125
|
+
resourceContext: resourceContext2 || "",
|
|
126
|
+
resourceState: resourceState2,
|
|
127
|
+
syncStatus: syncStatus2,
|
|
128
|
+
createdAt: createdAt2,
|
|
129
|
+
origin: origin2
|
|
130
|
+
};
|
|
131
|
+
}, [
|
|
132
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-properties"],
|
|
133
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-manifest"],
|
|
134
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-object"],
|
|
135
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-cci-resource-context"],
|
|
136
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-state"],
|
|
137
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-sync-status"],
|
|
138
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-created-at"],
|
|
139
|
+
entity.metadata.annotations?.["terasky.backstage.io/vcf-automation-resource-origin"]
|
|
140
|
+
]);
|
|
141
|
+
const needsApiCall = !annotationData.resourceData || !annotationData.manifest || !annotationData.objectData;
|
|
142
|
+
const { value: apiResourceData, loading, error } = useAsync(async () => {
|
|
143
|
+
if (!needsApiCall || !resourceId) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
if (isStandalone) {
|
|
148
|
+
const response = await api.getSupervisorResource(resourceId, instanceName);
|
|
149
|
+
if (response) {
|
|
150
|
+
return {
|
|
151
|
+
id: response.id,
|
|
152
|
+
properties: {
|
|
153
|
+
manifest: {
|
|
154
|
+
apiVersion: response.apiVersion,
|
|
155
|
+
kind: response.kind,
|
|
156
|
+
metadata: response.metadata,
|
|
157
|
+
spec: response.spec
|
|
158
|
+
},
|
|
159
|
+
object: {
|
|
160
|
+
apiVersion: response.apiVersion,
|
|
161
|
+
kind: response.kind,
|
|
162
|
+
metadata: response.metadata,
|
|
163
|
+
spec: response.spec,
|
|
164
|
+
status: response.status
|
|
165
|
+
},
|
|
166
|
+
context: JSON.stringify({
|
|
167
|
+
namespace: response.metadata.namespace,
|
|
168
|
+
apiVersion: response.apiVersion,
|
|
169
|
+
kind: response.kind,
|
|
170
|
+
standalone: true
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
if (!deploymentId) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const response = await api.getDeploymentResources(deploymentId, instanceName);
|
|
180
|
+
let resources = null;
|
|
181
|
+
if (response) {
|
|
182
|
+
if (Array.isArray(response)) {
|
|
183
|
+
resources = response;
|
|
184
|
+
} else if (response.content && Array.isArray(response.content)) {
|
|
185
|
+
resources = response.content;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (resources) {
|
|
189
|
+
return resources.find((r) => r.id === resourceId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
} catch (apiError) {
|
|
194
|
+
console.error("Failed to fetch resource data:", apiError);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}, [needsApiCall, isStandalone, deploymentId, resourceId, instanceName]);
|
|
198
|
+
const resourceData = annotationData.resourceData || apiResourceData;
|
|
199
|
+
const manifest = annotationData.manifest || apiResourceData?.properties?.manifest;
|
|
200
|
+
const objectData = annotationData.objectData || apiResourceData?.properties?.object;
|
|
201
|
+
const resourceContext = annotationData.resourceContext || apiResourceData?.properties?.context;
|
|
202
|
+
const resourceState = annotationData.resourceState;
|
|
203
|
+
const syncStatus = annotationData.syncStatus;
|
|
204
|
+
const createdAt = annotationData.createdAt;
|
|
205
|
+
const origin = annotationData.origin;
|
|
206
|
+
const resourceKind = manifest?.kind || objectData?.kind;
|
|
207
|
+
const resourceName = manifest?.metadata?.name || objectData?.metadata?.name;
|
|
208
|
+
const namespaceName = manifest?.metadata?.namespace || objectData?.metadata?.namespace;
|
|
209
|
+
const apiVersion = manifest?.apiVersion || objectData?.apiVersion;
|
|
210
|
+
const extractNamespaceUrnId = useCallback((endpoint) => {
|
|
211
|
+
const match = endpoint.match(/\/namespaces\/(urn:vcloud:namespace:[^\/]+)/);
|
|
212
|
+
return match ? match[1] : void 0;
|
|
213
|
+
}, []);
|
|
214
|
+
const findCCINamespaceParent = useCallback(async (currentEntity, depth = 0) => {
|
|
215
|
+
if (depth >= 3) return void 0;
|
|
216
|
+
if (currentEntity.spec?.type === "CCI.Supervisor.Namespace") {
|
|
217
|
+
const endpoint = currentEntity.metadata?.annotations?.["terasky.backstage.io/vcf-automation-cci-namespace-endpoint"];
|
|
218
|
+
if (endpoint) {
|
|
219
|
+
return extractNamespaceUrnId(endpoint);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const parentRef = currentEntity.spec?.subcomponentOf;
|
|
223
|
+
if (!parentRef) return void 0;
|
|
224
|
+
try {
|
|
225
|
+
const parentEntity = await catalogApi.getEntityByRef(parentRef);
|
|
226
|
+
if (parentEntity) {
|
|
227
|
+
return await findCCINamespaceParent(parentEntity, depth + 1);
|
|
228
|
+
}
|
|
229
|
+
} catch (error2) {
|
|
230
|
+
console.warn("Failed to fetch parent entity:", parentRef, error2);
|
|
231
|
+
}
|
|
232
|
+
return void 0;
|
|
233
|
+
}, [catalogApi, extractNamespaceUrnId]);
|
|
234
|
+
const { value: namespaceUrnId } = useAsync(async () => {
|
|
235
|
+
if (!namespaceName) return void 0;
|
|
236
|
+
if (isStandalone) {
|
|
237
|
+
let contextData = null;
|
|
238
|
+
try {
|
|
239
|
+
contextData = typeof resourceContext === "string" ? JSON.parse(resourceContext || "{}") : resourceContext;
|
|
240
|
+
} catch (error2) {
|
|
241
|
+
console.log("Debug - Resource context is not JSON, treating as string:", resourceContext);
|
|
242
|
+
contextData = null;
|
|
243
|
+
}
|
|
244
|
+
return contextData?.namespaceUrnId || namespaceName;
|
|
245
|
+
} else {
|
|
246
|
+
const urnId = await findCCINamespaceParent(entity);
|
|
247
|
+
return urnId || namespaceName;
|
|
248
|
+
}
|
|
249
|
+
}, [namespaceName, resourceContext, isStandalone, entity, findCCINamespaceParent]);
|
|
250
|
+
const loadManifestForEditing = useCallback(async () => {
|
|
251
|
+
if (!canEditResource || !namespaceName || !resourceName || !namespaceUrnId || !apiVersion || !resourceKind) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
setIsLoadingManifest(true);
|
|
255
|
+
try {
|
|
256
|
+
const manifestResponse = await api.getSupervisorResourceManifest(
|
|
257
|
+
namespaceUrnId,
|
|
258
|
+
namespaceName,
|
|
259
|
+
resourceName,
|
|
260
|
+
apiVersion,
|
|
261
|
+
resourceKind,
|
|
262
|
+
instanceName
|
|
263
|
+
);
|
|
264
|
+
setOriginalManifest(manifestResponse);
|
|
265
|
+
const yamlContent = yaml.dump(manifestResponse, {
|
|
266
|
+
indent: 2,
|
|
267
|
+
lineWidth: -1,
|
|
268
|
+
noRefs: true,
|
|
269
|
+
sortKeys: false
|
|
270
|
+
});
|
|
271
|
+
setEditingYaml(yamlContent);
|
|
272
|
+
setYamlValidationError("");
|
|
273
|
+
} catch (error2) {
|
|
274
|
+
setSnackbar({
|
|
275
|
+
open: true,
|
|
276
|
+
message: `Failed to fetch resource manifest: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
|
|
277
|
+
severity: "error"
|
|
278
|
+
});
|
|
279
|
+
} finally {
|
|
280
|
+
setIsLoadingManifest(false);
|
|
281
|
+
}
|
|
282
|
+
}, [canEditResource, namespaceName, resourceName, namespaceUrnId, apiVersion, resourceKind, instanceName, api]);
|
|
283
|
+
const validateYaml = useCallback((yamlString) => {
|
|
284
|
+
try {
|
|
285
|
+
yaml.load(yamlString);
|
|
286
|
+
setYamlValidationError("");
|
|
287
|
+
return true;
|
|
288
|
+
} catch (error2) {
|
|
289
|
+
const errorMessage = error2 instanceof Error ? error2.message : "Invalid YAML syntax";
|
|
290
|
+
setYamlValidationError(errorMessage);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}, []);
|
|
294
|
+
const handleYamlChange = useCallback((value) => {
|
|
295
|
+
setEditingYaml(value);
|
|
296
|
+
if (value.trim()) {
|
|
297
|
+
validateYaml(value);
|
|
298
|
+
} else {
|
|
299
|
+
setYamlValidationError("");
|
|
300
|
+
}
|
|
301
|
+
}, [validateYaml]);
|
|
302
|
+
const handleSaveResource = useCallback(async () => {
|
|
303
|
+
if (!originalManifest || !namespaceName || !resourceName || !namespaceUrnId || !apiVersion || !resourceKind) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
setIsSaving(true);
|
|
307
|
+
setConfirmDialogOpen(false);
|
|
308
|
+
try {
|
|
309
|
+
const updatedManifest = yaml.load(editingYaml);
|
|
310
|
+
await api.updateSupervisorResourceManifest(
|
|
311
|
+
namespaceUrnId,
|
|
312
|
+
namespaceName,
|
|
313
|
+
resourceName,
|
|
314
|
+
apiVersion,
|
|
315
|
+
resourceKind,
|
|
316
|
+
updatedManifest,
|
|
317
|
+
instanceName
|
|
318
|
+
);
|
|
319
|
+
setSnackbar({
|
|
320
|
+
open: true,
|
|
321
|
+
message: "Resource manifest updated successfully",
|
|
322
|
+
severity: "success"
|
|
323
|
+
});
|
|
324
|
+
setTimeout(() => window.location.reload(), 1e3);
|
|
325
|
+
} catch (error2) {
|
|
326
|
+
setSnackbar({
|
|
327
|
+
open: true,
|
|
328
|
+
message: `Failed to update resource manifest: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
|
|
329
|
+
severity: "error"
|
|
330
|
+
});
|
|
331
|
+
} finally {
|
|
332
|
+
setIsSaving(false);
|
|
333
|
+
}
|
|
334
|
+
}, [originalManifest, namespaceName, resourceName, namespaceUrnId, apiVersion, resourceKind, editingYaml, instanceName, api]);
|
|
335
|
+
const handleCancelEditing = useCallback(() => {
|
|
336
|
+
setEditingYaml("");
|
|
337
|
+
setOriginalManifest(null);
|
|
338
|
+
setYamlValidationError("");
|
|
339
|
+
}, []);
|
|
340
|
+
const handleCloseSnackbar = useCallback(() => {
|
|
341
|
+
setSnackbar((prev) => ({ ...prev, open: false }));
|
|
342
|
+
}, []);
|
|
69
343
|
const handleTabChange = (_event, newValue) => {
|
|
70
344
|
setTabValue(newValue);
|
|
345
|
+
if (newValue === 5 && canEditResource && !editingYaml && !isLoadingManifest) {
|
|
346
|
+
loadManifestForEditing();
|
|
347
|
+
}
|
|
71
348
|
};
|
|
72
|
-
if (
|
|
349
|
+
if (loading) {
|
|
350
|
+
return /* @__PURE__ */ jsx(InfoCard, { title: "CCI Supervisor Resource Details", children: /* @__PURE__ */ jsx(Typography, { children: "Loading resource details..." }) });
|
|
351
|
+
}
|
|
352
|
+
if (error) {
|
|
353
|
+
return /* @__PURE__ */ jsx(InfoCard, { title: "CCI Supervisor Resource Details", children: /* @__PURE__ */ jsxs(Typography, { color: "error", children: [
|
|
354
|
+
"Error loading resource details: ",
|
|
355
|
+
error.message
|
|
356
|
+
] }) });
|
|
357
|
+
}
|
|
358
|
+
if (!resourceData && !manifest && !objectData) {
|
|
73
359
|
return /* @__PURE__ */ jsx(InfoCard, { title: "CCI Supervisor Resource Details", children: /* @__PURE__ */ jsx(Typography, { children: "No resource data available." }) });
|
|
74
360
|
}
|
|
75
361
|
const renderStatusIcon = (conditionStatus) => {
|
|
@@ -109,7 +395,7 @@ const VCFAutomationCCIResourceDetails = () => {
|
|
|
109
395
|
noRefs: true,
|
|
110
396
|
sortKeys: false
|
|
111
397
|
});
|
|
112
|
-
} catch (
|
|
398
|
+
} catch (error2) {
|
|
113
399
|
return JSON.stringify(data, null, 2);
|
|
114
400
|
}
|
|
115
401
|
};
|
|
@@ -229,7 +515,8 @@ const VCFAutomationCCIResourceDetails = () => {
|
|
|
229
515
|
/* @__PURE__ */ jsx(Tab, { label: "Manifest Details" }),
|
|
230
516
|
/* @__PURE__ */ jsx(Tab, { label: "Object Status" }),
|
|
231
517
|
/* @__PURE__ */ jsx(Tab, { label: "Conditions" }),
|
|
232
|
-
/* @__PURE__ */ jsx(Tab, { label: "YAML Views" })
|
|
518
|
+
/* @__PURE__ */ jsx(Tab, { label: "YAML Views" }),
|
|
519
|
+
canEditResource && resourceName && namespaceName && namespaceUrnId && apiVersion && /* @__PURE__ */ jsx(Tab, { label: "Edit Manifest" })
|
|
233
520
|
] }),
|
|
234
521
|
/* @__PURE__ */ jsx(TabPanel, { value: tabValue, index: 0, children: /* @__PURE__ */ jsxs(Grid, { container: true, spacing: 3, children: [
|
|
235
522
|
/* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(StructuredMetadataTable, { metadata: basicInfo }) }),
|
|
@@ -350,7 +637,101 @@ const VCFAutomationCCIResourceDetails = () => {
|
|
|
350
637
|
}
|
|
351
638
|
) }) })
|
|
352
639
|
] }) })
|
|
353
|
-
] }) })
|
|
640
|
+
] }) }),
|
|
641
|
+
canEditResource && resourceName && namespaceName && namespaceUrnId && apiVersion && /* @__PURE__ */ jsx(TabPanel, { value: tabValue, index: 5, children: /* @__PURE__ */ jsx(Grid, { container: true, spacing: 3, children: /* @__PURE__ */ jsxs(Grid, { item: true, xs: 12, children: [
|
|
642
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h6", className: classes.sectionTitle, children: "Edit Resource Manifest" }),
|
|
643
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "textSecondary", gutterBottom: true, children: [
|
|
644
|
+
resourceName,
|
|
645
|
+
" (",
|
|
646
|
+
resourceKind,
|
|
647
|
+
")"
|
|
648
|
+
] }),
|
|
649
|
+
isLoadingManifest ? /* @__PURE__ */ jsx(Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "400px", children: /* @__PURE__ */ jsx(Typography, { children: "Loading manifest..." }) }) : /* @__PURE__ */ jsxs(Box, { className: classes.yamlEditorContainer, children: [
|
|
650
|
+
/* @__PURE__ */ jsx(Box, { className: classes.monacoEditor, children: /* @__PURE__ */ jsx(
|
|
651
|
+
Editor,
|
|
652
|
+
{
|
|
653
|
+
height: "100%",
|
|
654
|
+
defaultLanguage: "yaml",
|
|
655
|
+
value: editingYaml,
|
|
656
|
+
onChange: (value) => handleYamlChange(value || ""),
|
|
657
|
+
theme: "vs-dark",
|
|
658
|
+
options: {
|
|
659
|
+
minimap: { enabled: false },
|
|
660
|
+
scrollBeyondLastLine: false,
|
|
661
|
+
fontSize: 14,
|
|
662
|
+
lineNumbers: "on",
|
|
663
|
+
wordWrap: "off",
|
|
664
|
+
automaticLayout: true,
|
|
665
|
+
tabSize: 2,
|
|
666
|
+
insertSpaces: true,
|
|
667
|
+
folding: true,
|
|
668
|
+
renderWhitespace: "selection"
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
) }),
|
|
672
|
+
/* @__PURE__ */ jsx(Box, { className: classes.validationStatus, children: yamlValidationError ? /* @__PURE__ */ jsxs(Typography, { className: classes.yamlValidationError, children: [
|
|
673
|
+
"\u26A0\uFE0F YAML Validation Error: ",
|
|
674
|
+
yamlValidationError
|
|
675
|
+
] }) : 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" }) }),
|
|
676
|
+
/* @__PURE__ */ jsxs(Box, { className: classes.editorActions, children: [
|
|
677
|
+
/* @__PURE__ */ jsx(
|
|
678
|
+
Button,
|
|
679
|
+
{
|
|
680
|
+
variant: "outlined",
|
|
681
|
+
onClick: handleCancelEditing,
|
|
682
|
+
disabled: isSaving,
|
|
683
|
+
children: "Cancel"
|
|
684
|
+
}
|
|
685
|
+
),
|
|
686
|
+
/* @__PURE__ */ jsx(
|
|
687
|
+
Button,
|
|
688
|
+
{
|
|
689
|
+
variant: "contained",
|
|
690
|
+
color: "primary",
|
|
691
|
+
onClick: () => setConfirmDialogOpen(true),
|
|
692
|
+
disabled: !editingYaml.trim() || !!yamlValidationError || isSaving,
|
|
693
|
+
children: isSaving ? "Saving..." : "Save Changes"
|
|
694
|
+
}
|
|
695
|
+
)
|
|
696
|
+
] })
|
|
697
|
+
] })
|
|
698
|
+
] }) }) }),
|
|
699
|
+
/* @__PURE__ */ jsxs(
|
|
700
|
+
Dialog,
|
|
701
|
+
{
|
|
702
|
+
open: confirmDialogOpen,
|
|
703
|
+
onClose: () => setConfirmDialogOpen(false),
|
|
704
|
+
maxWidth: "sm",
|
|
705
|
+
fullWidth: true,
|
|
706
|
+
children: [
|
|
707
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Confirm Changes" }),
|
|
708
|
+
/* @__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." }) }),
|
|
709
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [
|
|
710
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setConfirmDialogOpen(false), color: "primary", children: "Cancel" }),
|
|
711
|
+
/* @__PURE__ */ jsx(
|
|
712
|
+
Button,
|
|
713
|
+
{
|
|
714
|
+
onClick: handleSaveResource,
|
|
715
|
+
color: "primary",
|
|
716
|
+
variant: "contained",
|
|
717
|
+
disabled: isSaving,
|
|
718
|
+
children: isSaving ? "Applying..." : "Apply Changes"
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
] })
|
|
722
|
+
]
|
|
723
|
+
}
|
|
724
|
+
),
|
|
725
|
+
/* @__PURE__ */ jsx(
|
|
726
|
+
Snackbar,
|
|
727
|
+
{
|
|
728
|
+
open: snackbar.open,
|
|
729
|
+
autoHideDuration: 6e3,
|
|
730
|
+
onClose: handleCloseSnackbar,
|
|
731
|
+
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
|
732
|
+
children: /* @__PURE__ */ jsx(Alert, { onClose: handleCloseSnackbar, severity: snackbar.severity, children: snackbar.message })
|
|
733
|
+
}
|
|
734
|
+
)
|
|
354
735
|
] }) })
|
|
355
736
|
] });
|
|
356
737
|
};
|