diff --git a/src/common/k8s-api/endpoints/secret.api.ts b/src/common/k8s-api/endpoints/secret.api.ts index 99200933ff..7c90c2e9c9 100644 --- a/src/common/k8s-api/endpoints/secret.api.ts +++ b/src/common/k8s-api/endpoints/secret.api.ts @@ -41,12 +41,9 @@ export interface ISecretRef { name: string; } -export interface Secret { +export interface SecretData extends KubeJsonApiData { type: SecretType; - data: { - [prop: string]: string; - token?: string; - }; + data?: Record; } export class Secret extends KubeObject { @@ -54,7 +51,10 @@ export class Secret extends KubeObject { static namespaced = true; static apiBase = "/api/v1/secrets"; - constructor(data: KubeJsonApiData) { + declare type: SecretType; + declare data: Record; + + constructor(data: SecretData) { super(data); autoBind(this); diff --git a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx new file mode 100644 index 0000000000..292f228cf9 --- /dev/null +++ b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import React from "react"; +import { render } from "@testing-library/react"; +import { SecretDetails } from "../secret-details"; +import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; + +jest.mock("../../kube-object-meta/kube-object-meta"); + + +describe("SecretDetails tests", () => { + it("should show the visibility toggle when the secret value is ''", () => { + const secret = new Secret({ + apiVersion: "v1", + kind: "secret", + metadata: { + name: "test", + resourceVersion: "1", + uid: "uid" + }, + data: { + foobar: "", + }, + type: SecretType.Opaque, + }); + const result = render(); + + expect(result.getByTestId("foobar-secret-entry").querySelector(".Icon")).toBeDefined(); + }); +}); diff --git a/src/renderer/components/+config-secrets/secret-details.tsx b/src/renderer/components/+config-secrets/secret-details.tsx index a8f4555ee1..30aff8640f 100644 --- a/src/renderer/components/+config-secrets/secret-details.tsx +++ b/src/renderer/components/+config-secrets/secret-details.tsx @@ -22,14 +22,13 @@ import "./secret-details.scss"; import React from "react"; -import isEmpty from "lodash/isEmpty"; import { autorun, observable, makeObservable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Input } from "../input"; import { Button } from "../button"; import { Notifications } from "../notifications"; -import { base64 } from "../../utils"; +import { base64, ObservableToggleSet } from "../../utils"; import { Icon } from "../icon"; import { secretsStore } from "./secrets.store"; import type { KubeObjectDetailsProps } from "../kube-object-details"; @@ -44,7 +43,7 @@ interface Props extends KubeObjectDetailsProps { export class SecretDetails extends React.Component { @observable isSaving = false; @observable data: { [name: string]: string } = {}; - @observable revealSecret: { [name: string]: boolean } = {}; + revealSecret = new ObservableToggleSet(); constructor(props: Props) { super(props); @@ -58,7 +57,7 @@ export class SecretDetails extends React.Component { if (secret) { this.data = secret.data; - this.revealSecret = {}; + this.revealSecret.clear(); } }) ]); @@ -82,6 +81,69 @@ export class SecretDetails extends React.Component { this.data[name] = encoded ? value : base64.encode(value); }; + renderSecret = ([name, value]: [string, string]) => { + let decodedVal: string | undefined; + + try { + decodedVal = base64.decode(value); + } catch { + /** + * The value failed to be decoded, so don't show the visibility + * toggle until the value is saved + */ + this.revealSecret.delete(name); + } + + const revealSecret = this.revealSecret.has(name); + + if (revealSecret && typeof decodedVal === "string") { + value = decodedVal; + } + + return ( +
+
{name}
+
+ this.editData(name, value, !revealSecret)} + /> + {typeof decodedVal === "string" && ( + this.revealSecret.toggle(name)} + /> + )} +
+
+ ); + }; + + renderData() { + const secrets = Object.entries(this.data); + + if (secrets.length === 0) { + return null; + } + + return ( + <> + + {secrets.map(this.renderSecret)} +