diff --git a/Makefile b/Makefile index cf6f988af1..c39b42468f 100644 --- a/Makefile +++ b/Makefile @@ -113,15 +113,11 @@ docs: .PHONY: clean-extensions clean-extensions: - $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist) - $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules) - $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm $(dir)/*.tgz || true) + rm -rf $(EXTENSIONS_DIR)/*/{dist,node_modules,*.tgz} .PHONY: clean-npm clean-npm: - rm -rf src/extensions/npm/extensions/dist - rm -rf src/extensions/npm/extensions/__mocks__ - rm -rf src/extensions/npm/extensions/node_modules + rm -rf src/extensions/npm/extensions/{dist,__mocks__,node_modules} .PHONY: clean clean: clean-npm clean-extensions diff --git a/package.json b/package.json index 212d8e65aa..371b3b82e3 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "chalk": "^4.1.0", "chokidar": "^3.4.3", "command-exists": "1.2.9", - "conf": "^7.0.1", + "conf": "^7.1.2", "crypto-js": "^4.1.1", "electron-devtools-installer": "^3.2.0", "electron-updater": "^4.6.0", @@ -258,7 +258,7 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", - "@sentry/react": "^6.8.0", + "@sentry/react": "^6.13.3", "@sentry/types": "^6.8.0", "@testing-library/dom": "^8.2.0", "@testing-library/jest-dom": "^5.14.1", diff --git a/src/common/k8s-api/__tests__/crd.test.ts b/src/common/k8s-api/__tests__/crd.test.ts index 459a7f67d9..a981cdda12 100644 --- a/src/common/k8s-api/__tests__/crd.test.ts +++ b/src/common/k8s-api/__tests__/crd.test.ts @@ -20,92 +20,108 @@ */ import { CustomResourceDefinition } from "../endpoints"; -import type { KubeObjectMetadata } from "../kube-object"; describe("Crds", () => { describe("getVersion", () => { - it("should get the first version name from the list of versions", () => { + it("should throw if none of the versions are served", () => { const crd = new CustomResourceDefinition({ - apiVersion: "foo", + apiVersion: "apiextensions.k8s.io/v1", kind: "CustomResourceDefinition", - metadata: {} as KubeObjectMetadata, + metadata: { + name: "foo", + resourceVersion: "12345", + uid: "12345", + }, + spec: { + versions: [ + { + name: "123", + served: false, + storage: false, + }, + { + name: "1234", + served: false, + storage: false, + }, + ], + }, }); - crd.spec = { - versions: [ - { - name: "123", - served: false, - storage: false, - } - ] - } as any; + expect(() => crd.getVersion()).toThrowError("Failed to find a version for CustomResourceDefinition foo"); + }); + + it("should should get the version that is both served and stored", () => { + const crd = new CustomResourceDefinition({ + apiVersion: "apiextensions.k8s.io/v1", + kind: "CustomResourceDefinition", + metadata: { + name: "foo", + resourceVersion: "12345", + uid: "12345", + }, + spec: { + versions: [ + { + name: "123", + served: true, + storage: true, + }, + { + name: "1234", + served: false, + storage: false, + }, + ], + }, + }); expect(crd.getVersion()).toBe("123"); }); - it("should get the first version name from the list of versions (length 2)", () => { + it("should should get the version that is both served and stored even with version field", () => { const crd = new CustomResourceDefinition({ - apiVersion: "foo", + apiVersion: "apiextensions.k8s.io/v1", kind: "CustomResourceDefinition", - metadata: {} as KubeObjectMetadata, + metadata: { + name: "foo", + resourceVersion: "12345", + uid: "12345", + }, + spec: { + version: "abc", + versions: [ + { + name: "123", + served: true, + storage: true, + }, + { + name: "1234", + served: false, + storage: false, + }, + ], + }, }); - crd.spec = { - versions: [ - { - name: "123", - served: false, - storage: false, - }, - { - name: "1234", - served: false, - storage: false, - } - ] - } as any; - expect(crd.getVersion()).toBe("123"); }); - it("should get the first version name from the list of versions (length 2) even with version field", () => { + it("should get the version name from the version field", () => { const crd = new CustomResourceDefinition({ - apiVersion: "foo", + apiVersion: "apiextensions.k8s.io/v1beta1", kind: "CustomResourceDefinition", - metadata: {} as KubeObjectMetadata, + metadata: { + name: "foo", + resourceVersion: "12345", + uid: "12345", + }, + spec: { + version: "abc", + } }); - crd.spec = { - version: "abc", - versions: [ - { - name: "123", - served: false, - storage: false, - }, - { - name: "1234", - served: false, - storage: false, - } - ] - } as any; - - expect(crd.getVersion()).toBe("123"); - }); - - it("should get the first version name from the version field", () => { - const crd = new CustomResourceDefinition({ - apiVersion: "foo", - kind: "CustomResourceDefinition", - metadata: {} as KubeObjectMetadata, - }); - - crd.spec = { - version: "abc" - } as any; - expect(crd.getVersion()).toBe("abc"); }); }); diff --git a/src/common/k8s-api/endpoints/crd.api.ts b/src/common/k8s-api/endpoints/crd.api.ts index b1ab179cc4..98c8d38df0 100644 --- a/src/common/k8s-api/endpoints/crd.api.ts +++ b/src/common/k8s-api/endpoints/crd.api.ts @@ -19,10 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { KubeObject } from "../kube-object"; +import { KubeCreationError, KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; import { crdResourcesURL } from "../../routes"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; +import type { KubeJsonApiData } from "../kube-json-api"; type AdditionalPrinterColumnsCommon = { name: string; @@ -39,10 +40,21 @@ type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & { JSONPath: string; }; +export interface CRDVersion { + name: string; + served: boolean; + storage: boolean; + schema?: object; // required in v1 but not present in v1beta + additionalPrinterColumns?: AdditionalPrinterColumnsV1[]; +} + export interface CustomResourceDefinition { spec: { group: string; - version?: string; // deprecated in v1 api + /** + * @deprecated for apiextensions.k8s.io/v1 but used previously + */ + version?: string; names: { plural: string; singular: string; @@ -50,19 +62,19 @@ export interface CustomResourceDefinition { listKind: string; }; scope: "Namespaced" | "Cluster" | string; - validation?: any; - versions?: { - name: string; - served: boolean; - storage: boolean; - schema?: unknown; // required in v1 but not present in v1beta - additionalPrinterColumns?: AdditionalPrinterColumnsV1[] - }[]; + /** + * @deprecated for apiextensions.k8s.io/v1 but used previously + */ + validation?: object; + versions?: CRDVersion[]; conversion: { strategy?: string; webhook?: any; }; - additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; // removed in v1 + /** + * @deprecated for apiextensions.k8s.io/v1 but used previously + */ + additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; }; status: { conditions: { @@ -83,11 +95,23 @@ export interface CustomResourceDefinition { }; } +export interface CRDApiData extends KubeJsonApiData { + spec: object; // TODO: make better +} + export class CustomResourceDefinition extends KubeObject { static kind = "CustomResourceDefinition"; static namespaced = false; static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions"; + constructor(data: CRDApiData) { + super(data); + + if (!data.spec || typeof data.spec !== "object") { + throw new KubeCreationError("Cannot create a CustomResourceDefinition from an object without spec", data); + } + } + getResourceUrl() { return crdResourcesURL({ params: { @@ -125,9 +149,36 @@ export class CustomResourceDefinition extends KubeObject { return this.spec.scope; } + getPreferedVersion(): CRDVersion { + // Prefer the modern `versions` over the legacy `version` + if (this.spec.versions) { + for (const version of this.spec.versions) { + /** + * If the version is not served then 404 errors will occur + * We should also prefer the storage version + */ + if (version.served && version.storage) { + return version; + } + } + } else if (this.spec.version) { + const { additionalPrinterColumns: apc } = this.spec; + const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc}) => ({ ...apc, jsonPath: JSONPath })); + + return { + name: this.spec.version, + served: true, + storage: true, + schema: this.spec.validation, + additionalPrinterColumns, + }; + } + + throw new Error(`Failed to find a version for CustomResourceDefinition ${this.metadata.name}`); + } + getVersion() { - // v1 has removed the spec.version property, if it is present it must match the first version - return this.spec.versions?.[0]?.name ?? this.spec.version; + return this.getPreferedVersion().name; } isNamespaced() { @@ -147,17 +198,14 @@ export class CustomResourceDefinition extends KubeObject { } getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] { - const columns = this.spec.versions?.find(a => this.getVersion() == a.name)?.additionalPrinterColumns - ?? this.spec.additionalPrinterColumns?.map(({ JSONPath, ...rest }) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape - ?? []; + const columns = this.getPreferedVersion().additionalPrinterColumns ?? []; return columns - .filter(column => column.name != "Age") - .filter(column => ignorePriority ? true : !column.priority); + .filter(column => column.name != "Age" && (ignorePriority || !column.priority)); } getValidation() { - return JSON.stringify(this.spec.validation ?? this.spec.versions?.[0]?.schema, null, 2); + return JSON.stringify(this.getPreferedVersion().schema, null, 2); } getConditions() { diff --git a/src/common/k8s-api/json-api.ts b/src/common/k8s-api/json-api.ts index 8690521099..dde8f5f5a2 100644 --- a/src/common/k8s-api/json-api.ts +++ b/src/common/k8s-api/json-api.ts @@ -27,8 +27,7 @@ import { stringify } from "querystring"; import { EventEmitter } from "../../common/event-emitter"; import logger from "../../common/logger"; -export interface JsonApiData { -} +export interface JsonApiData {} export interface JsonApiError { code?: number; diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts index 3de5f72bfe..67faa8a50d 100644 --- a/src/common/user-store/preferences-helpers.ts +++ b/src/common/user-store/preferences-helpers.ts @@ -259,6 +259,32 @@ const editorConfiguration: PreferenceDescription = { + fromStore(val) { + return updateChannels.has(val) ? val : defaultUpdateChannel; + }, + toStore(val) { + if (!updateChannels.has(val) || val === defaultUpdateChannel) { + return undefined; + } + + return val; + } +}; + type PreferencesModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never; type UserStoreModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never; @@ -287,4 +313,10 @@ export const DESCRIPTORS = { syncKubeconfigEntries, editorConfiguration, terminalCopyOnSelect, + updateChannel, +}; + +export const CONSTANTS = { + defaultUpdateChannel, + updateChannels, }; diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts index 642d31e1d6..a42661b280 100644 --- a/src/common/user-store/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -29,7 +29,7 @@ import { kubeConfigDefaultPath } from "../kube-helpers"; import { appEventBus } from "../event-bus"; import path from "path"; import { ObservableToggleSet, toJS } from "../../renderer/utils"; -import { defaultEditorConfig, DESCRIPTORS, EditorConfiguration, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers"; +import { defaultEditorConfig, DESCRIPTORS, EditorConfiguration, KubeconfigSyncValue, UserPreferencesModel, CONSTANTS } from "./preferences-helpers"; import logger from "../../main/logger"; import { getPath } from "../utils/getPath"; @@ -73,6 +73,7 @@ export class UserStore extends BaseStore /* implements UserStore @observable downloadBinariesPath?: string; @observable kubectlBinariesPath?: string; @observable terminalCopyOnSelect: boolean; + @observable updateChannel?: string; /** * Download kubectl binaries matching cluster version @@ -104,6 +105,10 @@ export class UserStore extends BaseStore /* implements UserStore return this.shell || process.env.SHELL || process.env.PTYSHELL; } + @computed get isAllowedToDowngrade(): boolean { + return this.updateChannel !== CONSTANTS.defaultUpdateChannel; + } + startMainReactions() { // track telemetry availability reaction(() => this.allowTelemetry, allowed => { @@ -194,6 +199,7 @@ export class UserStore extends BaseStore /* implements UserStore this.syncKubeconfigEntries.replace(DESCRIPTORS.syncKubeconfigEntries.fromStore(preferences?.syncKubeconfigEntries)); this.editorConfiguration = DESCRIPTORS.editorConfiguration.fromStore(preferences?.editorConfiguration); this.terminalCopyOnSelect = DESCRIPTORS.terminalCopyOnSelect.fromStore(preferences?.terminalCopyOnSelect); + this.updateChannel = DESCRIPTORS.updateChannel.fromStore(preferences?.updateChannel); } toJSON(): UserStoreModel { @@ -216,6 +222,7 @@ export class UserStore extends BaseStore /* implements UserStore syncKubeconfigEntries: DESCRIPTORS.syncKubeconfigEntries.toStore(this.syncKubeconfigEntries), editorConfiguration: DESCRIPTORS.editorConfiguration.toStore(this.editorConfiguration), terminalCopyOnSelect: DESCRIPTORS.terminalCopyOnSelect.toStore(this.terminalCopyOnSelect), + updateChannel: DESCRIPTORS.updateChannel.toStore(this.updateChannel), }, }; diff --git a/src/main/app-updater.ts b/src/main/app-updater.ts index ab8a4c509b..d22f96d65d 100644 --- a/src/main/app-updater.ts +++ b/src/main/app-updater.ts @@ -27,6 +27,7 @@ import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMess import { once } from "lodash"; import { ipcMain } from "electron"; import { nextUpdateChannel } from "./utils/update-channel"; +import { UserStore } from "../common/user-store"; const updateChannel = autoUpdater.channel; let installVersion: null | string = null; @@ -58,9 +59,13 @@ export const startUpdateChecking = once(function (interval = 1000 * 60 * 60 * 24 return; } + const us = UserStore.getInstance(); + autoUpdater.logger = logger; autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = false; + autoUpdater.channel = us.updateChannel; + autoUpdater.allowDowngrade = us.isAllowedToDowngrade; autoUpdater .on("update-available", (info: UpdateInfo) => { diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index a5c9179499..e72cbd59cc 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -160,6 +160,10 @@ export class ClusterManager extends Singleton { return "connecting"; } + if (entity?.status?.phase) { + return entity.status.phase; + } + return "disconnected"; })(); diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index b8a18a1021..571628789e 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -43,8 +43,8 @@ export async function listReleases(pathToKubeconfig: string, namespace?: string) }); return output; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } } @@ -72,8 +72,8 @@ export async function installChart(chart: string, values: any, name: string | un namespace } }; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } finally { await fse.unlink(fileName); } @@ -93,8 +93,8 @@ export async function upgradeRelease(name: string, chart: string, values: any, n log: stdout, release: getRelease(name, namespace, cluster) }; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } finally { await fse.unlink(fileName); } @@ -111,8 +111,8 @@ export async function getRelease(name: string, namespace: string, cluster: Clust release.resources = await getResources(name, namespace, cluster); return release; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } } @@ -122,8 +122,8 @@ export async function deleteRelease(name: string, namespace: string, pathToKubec const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); return stdout; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } } @@ -139,8 +139,8 @@ export async function getValues(name: string, { namespace, all = false, pathToKu const { stdout } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all" : ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); return stdout; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } } @@ -150,8 +150,8 @@ export async function getHistory(name: string, namespace: string, pathToKubeconf const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); return JSON.parse(stdout); - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } } @@ -161,8 +161,8 @@ export async function rollback(name: string, namespace: string, revision: number const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`); return stdout; - } catch ({ stderr }) { - throw stderr; + } catch (error) { + throw error?.stderr || error; } } diff --git a/src/main/routes/helm-route.ts b/src/main/routes/helm-route.ts index 2f217a3193..46112d7111 100644 --- a/src/main/routes/helm-route.ts +++ b/src/main/routes/helm-route.ts @@ -41,7 +41,7 @@ export class HelmApiRoute { respondJson(response, chart); } catch (error) { - respondText(response, error, 422); + respondText(response, error?.toString() || "Error getting chart", 422); } } @@ -53,7 +53,7 @@ export class HelmApiRoute { respondJson(response, values); } catch (error) { - respondText(response, error, 422); + respondText(response, error?.toString() || "Error getting chart values", 422); } } @@ -66,7 +66,7 @@ export class HelmApiRoute { respondJson(response, result, 201); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error installing chart", 422); } } @@ -79,7 +79,7 @@ export class HelmApiRoute { respondJson(response, result); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error updating chart", 422); } } @@ -92,7 +92,7 @@ export class HelmApiRoute { respondJson(response, result); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error rolling back chart", 422); } } @@ -105,7 +105,7 @@ export class HelmApiRoute { respondJson(response, result); } catch(error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error listing release", 422); } } @@ -118,7 +118,7 @@ export class HelmApiRoute { respondJson(response, result); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error getting release", 422); } } @@ -132,7 +132,7 @@ export class HelmApiRoute { respondText(response, result); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error getting release values", 422); } } @@ -145,7 +145,7 @@ export class HelmApiRoute { respondJson(response, result); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error getting release history", 422); } } @@ -158,7 +158,7 @@ export class HelmApiRoute { respondJson(response, result); } catch (error) { logger.debug(error); - respondText(response, error, 422); + respondText(response, error?.toString() || "Error deleting release", 422); } } } diff --git a/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx b/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx index 041f7b1cbd..72a6f1ea34 100644 --- a/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx +++ b/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx @@ -23,8 +23,8 @@ import "./helm-chart-details.scss"; import React, { Component } from "react"; import { getChartDetails, HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api"; -import { observable, autorun, makeObservable } from "mobx"; -import { observer } from "mobx-react"; +import { observable, makeObservable, reaction } from "mobx"; +import { disposeOnUnmount, observer } from "mobx-react"; import { Drawer, DrawerItem } from "../drawer"; import { boundMethod, stopPropagation } from "../../utils"; import { MarkdownViewer } from "../markdown-viewer"; @@ -64,20 +64,24 @@ export class HelmChartDetails extends Component { this.abortController?.abort(); } - chartUpdater = autorun(() => { - this.selectedChart = null; - const { chart: { name, repo, version } } = this.props; + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.chart, async ({ name, repo, version }) => { + try { + const { readme, versions } = await getChartDetails(repo, name, { version }); - getChartDetails(repo, name, { version }) - .then(result => { - this.readme = result.readme; - this.chartVersions = result.versions; - this.selectedChart = result.versions[0]; - }) - .catch(error => { - this.error = error; - }); - }); + this.readme = readme; + this.chartVersions = versions; + this.selectedChart = versions[0]; + } catch (error) { + this.error = error; + this.selectedChart = null; + } + }, { + fireImmediately: true, + }), + ]); + } @boundMethod async onVersionChange({ value: chart }: SelectOption) { @@ -135,7 +139,7 @@ export class HelmChartDetails extends Component { value: chart, }))} isOptionDisabled={({ value: chart }) => chart.deprecated} - value={selectedChart.getVersion()} + value={selectedChart} onChange={onVersionChange} /> @@ -170,10 +174,6 @@ export class HelmChartDetails extends Component { } renderContent() { - if (!this.selectedChart) { - return ; - } - if (this.error) { return (
@@ -182,6 +182,10 @@ export class HelmChartDetails extends Component { ); } + if (!this.selectedChart) { + return ; + } + return (
{this.renderIntroduction()} diff --git a/src/renderer/components/+apps-releases/release-details.scss b/src/renderer/components/+apps-releases/release-details.scss index 112ee6b578..759bd5cec4 100644 --- a/src/renderer/components/+apps-releases/release-details.scss +++ b/src/renderer/components/+apps-releases/release-details.scss @@ -26,6 +26,11 @@ align-items: center; } + .loading-error { + color: red; + text-align: center; + } + .status { @include release-status-bgs; } diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx index 5e17ca1cdd..0f8000a347 100644 --- a/src/renderer/components/+apps-releases/release-details.tsx +++ b/src/renderer/components/+apps-releases/release-details.tsx @@ -55,12 +55,13 @@ interface Props { @observer export class ReleaseDetails extends Component { - @observable details: IReleaseDetails; + @observable details: IReleaseDetails | null = null; @observable values = ""; @observable valuesLoading = false; @observable showOnlyUserSuppliedValues = false; @observable saving = false; @observable releaseSecret: Secret; + @observable error?: string = undefined; componentDidMount() { disposeOnUnmount(this, [ @@ -95,9 +96,13 @@ export class ReleaseDetails extends Component { async loadDetails() { const { release } = this.props; - - this.details = null; - this.details = await getRelease(release.getName(), release.getNs()); + + try { + this.details = null; + this.details = await getRelease(release.getName(), release.getNs()); + } catch (error) { + this.error = `Failed to get release details: ${error}`; + } } async loadValues() { @@ -234,11 +239,18 @@ export class ReleaseDetails extends Component { renderContent() { const { release } = this.props; - const { details } = this; if (!release) return null; - if (!details) { + if (this.error) { + return ( +
+ {this.error} +
+ ); + } + + if (!this.details) { return ; } diff --git a/src/renderer/components/+apps-releases/release-menu.tsx b/src/renderer/components/+apps-releases/release-menu.tsx index 0b8d4b8c91..a53298460f 100644 --- a/src/renderer/components/+apps-releases/release-menu.tsx +++ b/src/renderer/components/+apps-releases/release-menu.tsx @@ -79,6 +79,7 @@ export class HelmReleaseMenu extends React.Component { {...menuProps} className={cssNames("HelmReleaseMenu", className)} removeAction={this.remove} + removeConfirmationMessage={() =>

Remove Helm Release {release.name}?

} > {this.renderContent()} diff --git a/src/renderer/components/+preferences/application.tsx b/src/renderer/components/+preferences/application.tsx index ba86dc2ea8..f99b65f306 100644 --- a/src/renderer/components/+preferences/application.tsx +++ b/src/renderer/components/+preferences/application.tsx @@ -29,11 +29,16 @@ import { Input } from "../input"; import { isWindows } from "../../../common/vars"; import { FormSwitch, Switcher } from "../switch"; import moment from "moment-timezone"; +import { CONSTANTS } from "../../../common/user-store/preferences-helpers"; const timezoneOptions: SelectOption[] = moment.tz.names().map(zone => ({ label: zone, value: zone, })); +const updateChannelOptions: SelectOption[] = Array.from( + CONSTANTS.updateChannels.entries(), + ([value, { label }]) => ({ value, label }), +); export const Application = observer(() => { const defaultShell = process.env.SHELL @@ -104,6 +109,18 @@ export const Application = observer(() => {
+
+ + (key: string, defaultValue: T, clusterId?: st .then(data => storage.data = data) .catch(() => null) // ignore empty / non-existing / invalid json files .finally(() => { - logger.info(`${logPrefix} loading finished for ${filePath}`); + if (!isTestEnv) { + logger.info(`${logPrefix} loading finished for ${filePath}`); + } storage.loaded = true; }); diff --git a/yarn.lock b/yarn.lock index 8aff786fd6..623de85892 100644 --- a/yarn.lock +++ b/yarn.lock @@ -985,6 +985,16 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@sentry/browser@6.13.3": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.13.3.tgz#d4511791b1e484ad48785eba3bce291fdf115c1e" + integrity sha512-jwlpsk2/u1cofvfYsjmqcnx50JJtf/T6HTgdW+ih8+rqWC5ABEZf4IiB/H+KAyjJ3wVzCOugMq5irL83XDCfqQ== + dependencies: + "@sentry/core" "6.13.3" + "@sentry/types" "6.13.3" + "@sentry/utils" "6.13.3" + tslib "^1.9.3" + "@sentry/browser@6.7.1": version "6.7.1" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.7.1.tgz#e01144a08984a486ecc91d7922cc457e9c9bd6b7" @@ -995,14 +1005,15 @@ "@sentry/utils" "6.7.1" tslib "^1.9.3" -"@sentry/browser@6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.8.0.tgz#023707cd2302f6818014e9a7e124856b2d064178" - integrity sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA== +"@sentry/core@6.13.3": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.13.3.tgz#5cbbb995128e793ebebcbf1d3b7514e0e5e8b221" + integrity sha512-obm3SjgCk8A7nB37b2AU1eq1q7gMoJRrGMv9VRIyfcG0Wlz/5lJ9O3ohUk+YZaaVfZMxXn6hFtsBiOWmlv7IIA== dependencies: - "@sentry/core" "6.8.0" - "@sentry/types" "6.8.0" - "@sentry/utils" "6.8.0" + "@sentry/hub" "6.13.3" + "@sentry/minimal" "6.13.3" + "@sentry/types" "6.13.3" + "@sentry/utils" "6.13.3" tslib "^1.9.3" "@sentry/core@6.7.1": @@ -1016,17 +1027,6 @@ "@sentry/utils" "6.7.1" tslib "^1.9.3" -"@sentry/core@6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.8.0.tgz#bfac76844deee9126460c18dc6166015992efdc3" - integrity sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA== - dependencies: - "@sentry/hub" "6.8.0" - "@sentry/minimal" "6.8.0" - "@sentry/types" "6.8.0" - "@sentry/utils" "6.8.0" - tslib "^1.9.3" - "@sentry/electron@^2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@sentry/electron/-/electron-2.5.0.tgz#4168ff04ee01cb5a99ce042f3435660a510c634d" @@ -1040,6 +1040,15 @@ "@sentry/utils" "6.7.1" tslib "^2.2.0" +"@sentry/hub@6.13.3": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.13.3.tgz#cc09623a69b5343315fdb61c7fdd0be74b72299f" + integrity sha512-eYppBVqvhs5cvm33snW2sxfcw6G20/74RbBn+E4WDo15hozis89kU7ZCJDOPkXuag3v1h9igns/kM6PNBb41dw== + dependencies: + "@sentry/types" "6.13.3" + "@sentry/utils" "6.13.3" + tslib "^1.9.3" + "@sentry/hub@6.7.1": version "6.7.1" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.7.1.tgz#d46d24deec67f0731a808ca16796e6765b371bc1" @@ -1049,15 +1058,6 @@ "@sentry/utils" "6.7.1" tslib "^1.9.3" -"@sentry/hub@6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.8.0.tgz#cb0f8509093919ed3c1ef98ef8cf63dc102a6524" - integrity sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg== - dependencies: - "@sentry/types" "6.8.0" - "@sentry/utils" "6.8.0" - tslib "^1.9.3" - "@sentry/integrations@^6.10.0": version "6.10.0" resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.10.0.tgz#f8f9e7efd55ec44d0408bd4493df1c9ceabaaa63" @@ -1068,6 +1068,15 @@ localforage "^1.8.1" tslib "^1.9.3" +"@sentry/minimal@6.13.3": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.13.3.tgz#a675a79bcc830142e4f95e6198a2efde2cd3901e" + integrity sha512-63MlYYRni3fs5Bh8XBAfVZ+ctDdWg0fapSTP1ydIC37fKvbE+5zhyUqwrEKBIiclEApg1VKX7bkKxVdu/vsFdw== + dependencies: + "@sentry/hub" "6.13.3" + "@sentry/types" "6.13.3" + tslib "^1.9.3" + "@sentry/minimal@6.7.1": version "6.7.1" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.7.1.tgz#babf85ee2f167e9dcf150d750d7a0b250c98449c" @@ -1077,15 +1086,6 @@ "@sentry/types" "6.7.1" tslib "^1.9.3" -"@sentry/minimal@6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.8.0.tgz#d6c3e4c96f231367aeb2b8a87a83b53d28e7c6db" - integrity sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ== - dependencies: - "@sentry/hub" "6.8.0" - "@sentry/types" "6.8.0" - tslib "^1.9.3" - "@sentry/node@6.7.1": version "6.7.1" resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.7.1.tgz#b09e2eca8e187168feba7bd865a23935bf0f5cc0" @@ -1101,15 +1101,15 @@ lru_map "^0.3.3" tslib "^1.9.3" -"@sentry/react@^6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.8.0.tgz#3cf4a2e1ef042ee227836e268e702e9cb3b67e41" - integrity sha512-yXNnDaVw8kIacbwQjA27w8DA74WxmDVw4RlUTJGtq35SDmWsaGN1mwvU+mE1u3zEg927QTCBYig2Zqk8Tdt/fQ== +"@sentry/react@^6.13.3": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.13.3.tgz#f9607e0a60d52efd0baa96a14e694b6059f9379a" + integrity sha512-fdfmD9XNpGDwdkeLyd+iq+kqtNeghpH3wiez2rD81ZBvrn70uKaO2/yYDE71AXC6fUOwQuJmdfAuqBcNJkYIEw== dependencies: - "@sentry/browser" "6.8.0" - "@sentry/minimal" "6.8.0" - "@sentry/types" "6.8.0" - "@sentry/utils" "6.8.0" + "@sentry/browser" "6.13.3" + "@sentry/minimal" "6.13.3" + "@sentry/types" "6.13.3" + "@sentry/utils" "6.13.3" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" @@ -1124,21 +1124,21 @@ "@sentry/utils" "6.7.1" tslib "^1.9.3" -"@sentry/types@6.10.0", "@sentry/types@^6.8.0": +"@sentry/types@6.10.0": version "6.10.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1" integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw== +"@sentry/types@6.13.3", "@sentry/types@^6.8.0": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.13.3.tgz#63ad5b6735b0dfd90b3a256a9f8e77b93f0f66b2" + integrity sha512-Vrz5CdhaTRSvCQjSyIFIaV9PodjAVFkzJkTRxyY7P77RcegMsRSsG1yzlvCtA99zG9+e6MfoJOgbOCwuZids5A== + "@sentry/types@6.7.1": version "6.7.1" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.1.tgz#c8263e1886df5e815570c4668eb40a1cfaa1c88b" integrity sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA== -"@sentry/types@6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.8.0.tgz#97fd531a0ed1e75e65b4a24b26509fb7c15eb7b8" - integrity sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA== - "@sentry/utils@6.10.0": version "6.10.0" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.10.0.tgz#839a099fa0a1f0ca0893c7ce8c55ba0608c1d80f" @@ -1147,6 +1147,14 @@ "@sentry/types" "6.10.0" tslib "^1.9.3" +"@sentry/utils@6.13.3": + version "6.13.3" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.13.3.tgz#188754d40afe693c3fcae410f9322531588a9926" + integrity sha512-zYFuFH3MaYtBZTeJ4Yajg7pDf0pM3MWs3+9k5my9Fd+eqNcl7dYQYJbT9gyC0HXK1QI4CAMNNlHNl4YXhF91ag== + dependencies: + "@sentry/types" "6.13.3" + tslib "^1.9.3" + "@sentry/utils@6.7.1": version "6.7.1" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.7.1.tgz#909184ad580f0f6375e1e4d4a6ffd33dfe64a4d1" @@ -1155,14 +1163,6 @@ "@sentry/types" "6.7.1" tslib "^1.9.3" -"@sentry/utils@6.8.0": - version "6.8.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.8.0.tgz#0ffafa5b69fe0cdeabad5c4a6cc68a426eaa6b37" - integrity sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA== - dependencies: - "@sentry/types" "6.8.0" - tslib "^1.9.3" - "@sideway/address@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d" @@ -2966,6 +2966,11 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +atomically@^1.3.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe" + integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== + auto-bind@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-2.1.1.tgz#8ae509671ecdfbd5009fc99b0f19ae9c3a2abf50" @@ -4176,12 +4181,13 @@ concurrently@^5.2.0: tree-kill "^1.2.2" yargs "^13.3.0" -conf@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/conf/-/conf-7.0.1.tgz#a3ddd860db0357219b9ccdf92fd618e161fe6848" - integrity sha512-UFA9EPFKK+tedCjz4JKQSxAN0/0r0rjURdq9N805JXWOhFzmr7fTLmG1cnsHCvyYYd65wRjf0KB8zg+WPelkvA== +conf@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/conf/-/conf-7.1.2.tgz#d9678a9d8f04de8bf5cd475105da8fdae49c2ec4" + integrity sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg== dependencies: ajv "^6.12.2" + atomically "^1.3.1" debounce-fn "^4.0.0" dot-prop "^5.2.0" env-paths "^2.2.0" @@ -4190,7 +4196,6 @@ conf@^7.0.1: onetime "^5.1.0" pkg-up "^3.1.0" semver "^7.3.2" - write-file-atomic "^3.0.3" config-chain@^1.1.11, config-chain@^1.1.12: version "1.1.12" @@ -10435,14 +10440,7 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== - dependencies: - mimic-fn "^2.1.0" - -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -14653,7 +14651,7 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.3: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: +write-file-atomic@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==