diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 3812ef450d..7c8118da1f 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -286,7 +286,13 @@ export abstract class KubeObjectStore extends ItemStore ensureObjectSelfLink(this.api, newItem); - return this.items[index] = newItem; + if (index < 0) { + this.items.push(newItem); + } else { + this.items[index] = newItem; + } + + return newItem; } async patch(item: T, patch: Patch): Promise { diff --git a/src/common/k8s-api/kube-object.ts b/src/common/k8s-api/kube-object.ts index 3011406fa4..bcbe6bf05f 100644 --- a/src/common/k8s-api/kube-object.ts +++ b/src/common/k8s-api/kube-object.ts @@ -192,7 +192,7 @@ export class KubeObject): Promise { // use unified resource-applier api for updating all k8s objects diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index 9e1a345e58..a305ff1091 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -62,7 +62,7 @@ export class ResourceApplier { args.push( "--type", "json", "--patch", JSON.stringify(patch), - "-o", "yaml" + "-o", "json" ); try { @@ -93,18 +93,17 @@ export class ResourceApplier { "-f", fileName, ]; - await fs.writeFile(fileName, content); - - logger.debug(`shooting manifests with with ${kubectlPath}`, { args }); + logger.debug(`shooting manifests with ${kubectlPath}`, { args }); const execEnv = { ...process.env }; const httpsProxy = this.cluster.preferences?.httpsProxy; if (httpsProxy) { - execEnv["HTTPS_PROXY"] = httpsProxy; + execEnv.HTTPS_PROXY = httpsProxy; } try { + await fs.writeFile(fileName, content); const { stdout } = await promiseExecFile(kubectlPath, args); return stdout; diff --git a/src/main/routes/resource-applier-route.ts b/src/main/routes/resource-applier-route.ts index 03d2f1f4bf..0adf916188 100644 --- a/src/main/routes/resource-applier-route.ts +++ b/src/main/routes/resource-applier-route.ts @@ -40,7 +40,7 @@ export class ResourceApplierApiRoute { const { response, cluster, payload } = request; try { - const resource = await new ResourceApplier(cluster).patch(payload.name, payload.kind, payload.ns, payload.patch); + const resource = await new ResourceApplier(cluster).patch(payload.name, payload.kind, payload.patch, payload.ns); respondJson(response, resource, 200); } catch (error) { diff --git a/src/main/utils/http-responses.ts b/src/main/utils/http-responses.ts index 36287806f1..5d57b6dd76 100644 --- a/src/main/utils/http-responses.ts +++ b/src/main/utils/http-responses.ts @@ -21,14 +21,37 @@ import type http from "http"; -export function respondJson(res: http.ServerResponse, content: any, status = 200) { - respond(res, JSON.stringify(content), "application/json", status); +/** + * Respond to a HTTP request with a body of JSON data + * @param res The HTTP response to write data to + * @param content The data or its JSON stringified version of it + * @param status [200] The status code to respond with + */ +export function respondJson(res: http.ServerResponse, content: Object | string, status = 200) { + const normalizedContent = typeof content === "object" + ? JSON.stringify(content) + : content; + + respond(res, normalizedContent, "application/json", status); } +/** + * Respond to a HTTP request with a body of plain text data + * @param res The HTTP response to write data to + * @param content The string data to respond with + * @param status [200] The status code to respond with + */ export function respondText(res: http.ServerResponse, content: string, status = 200) { respond(res, content, "text/plain", status); } +/** + * Respond to a HTTP request with a body of plain text data + * @param res The HTTP response to write data to + * @param content The string data to respond with + * @param contentType The HTTP Content-Type header value + * @param status [200] The status code to respond with + */ export function respond(res: http.ServerResponse, content: string, contentType: string, status = 200) { res.setHeader("Content-Type", contentType); res.statusCode = status; diff --git a/src/renderer/components/+config-maps/config-map-details.tsx b/src/renderer/components/+config-maps/config-map-details.tsx index abbd31c97a..6150aa1ad9 100644 --- a/src/renderer/components/+config-maps/config-map-details.tsx +++ b/src/renderer/components/+config-maps/config-map-details.tsx @@ -72,6 +72,8 @@ export class ConfigMapDetails extends React.Component { <>ConfigMap {configMap.getName()} successfully updated.

); + } catch (error) { + Notifications.error(`Failed to save config map: ${error}`); } finally { this.isSaving = false; } diff --git a/src/renderer/components/dock/create-resource.tsx b/src/renderer/components/dock/create-resource.tsx index fef151da7d..c7de411e3c 100644 --- a/src/renderer/components/dock/create-resource.tsx +++ b/src/renderer/components/dock/create-resource.tsx @@ -34,9 +34,9 @@ import type { DockTab } from "./dock.store"; import { EditorPanel } from "./editor-panel"; import { InfoPanel } from "./info-panel"; import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api"; -import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api"; import { Notifications } from "../notifications"; import { monacoModelsManager } from "./monaco-model-manager"; +import logger from "../../../common/logger"; interface Props { className?: string; @@ -95,7 +95,7 @@ export class CreateResource extends React.Component { }); }; - create = async () => { + create = async (): Promise => { if (this.error || !this.data.trim()) { // do not save when field is empty or there is an error return null; @@ -103,31 +103,31 @@ export class CreateResource extends React.Component { // skip empty documents if "---" pasted at the beginning or end const resources = jsYaml.safeLoadAll(this.data).filter(Boolean); - const createdResources: string[] = []; - const errors: string[] = []; - await Promise.all( - resources.map(data => { - return resourceApplierApi.update(data) - .then(item => createdResources.push(item.metadata.name)) - .catch((err: JsonApiErrorParsed) => errors.push(err.toString())); - }) - ); - - if (errors.length) { - errors.forEach(error => Notifications.error(error)); - if (!createdResources.length) throw errors[0]; + if (resources.length === 0) { + return void logger.info("Nothing to create"); } - const successMessage = ( -

- {createdResources.length === 1 ? "Resource" : "Resources"}{" "} - {createdResources.join(", ")} successfully created -

- ); - Notifications.ok(successMessage); + const createdResources: string[] = []; - return successMessage; + for (const result of await Promise.allSettled(resources.map(resourceApplierApi.update))) { + if (result.status === "fulfilled") { + createdResources.push(result.value.metadata.name); + } else { + Notifications.error(result.reason.toString()); + } + } + + if (createdResources.length > 0) { + Notifications.ok(( +

+ {createdResources.length === 1 ? "Resource" : "Resources"}{" "} + {createdResources.join(", ")} successfully created +

+ )); + } + + return undefined; }; renderControls(){ diff --git a/src/renderer/components/dock/edit-resource.tsx b/src/renderer/components/dock/edit-resource.tsx index dec46af3fe..6cd3a3afdc 100644 --- a/src/renderer/components/dock/edit-resource.tsx +++ b/src/renderer/components/dock/edit-resource.tsx @@ -98,9 +98,11 @@ export class EditResource extends React.Component { return null; } const store = editResourceStore.getStore(this.tabId); - const currentEdit = yaml.safeLoad(this.draft); + const currentVersion = yaml.safeLoad(this.draft); const firstVersion = yaml.safeLoad(editResourceStore.getData(this.tabId).firstDraft ?? this.draft); - const patches = createPatch(firstVersion, currentEdit); + const patches = createPatch(firstVersion, currentVersion); + + console.log({ currentVersion, firstVersion, patches }); const updatedResource = await store.patch(this.resource, patches); return ( @@ -127,9 +129,9 @@ export class EditResource extends React.Component { submittingMessage="Applying.." controls={(
- Kind: + Kind: Name: - Namespace: + Namespace:
)} /> diff --git a/src/renderer/components/dock/editor-panel.tsx b/src/renderer/components/dock/editor-panel.tsx index 31ccc5b2e2..bbf0b96e53 100644 --- a/src/renderer/components/dock/editor-panel.tsx +++ b/src/renderer/components/dock/editor-panel.tsx @@ -91,10 +91,7 @@ export class EditorPanel extends React.Component { onChange = (value: string) => { this.validate(value); - - if (this.props.onChange) { - this.props.onChange(value, this.yamlError); - } + this.props.onChange?.(value, this.yamlError); }; render() { diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.tsx index 1848fa2906..ac2a99cc88 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.tsx @@ -44,15 +44,11 @@ export class KubeObjectMenu extends React.Component