Stop using HelmCli from Renderer (#4861)

* Introduce way for execute file

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make typing of HelmRepo shared

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce way to get Helm environment values

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce function to read YAML file

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for listing active helm repositories in preferences

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make sense in name of injectable

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce helper for opening and selecting values of select

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for activating, deactivating public helm repositories in preferences

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for deactivating helm repository from list of active repositories

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing global overrides

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make some tests more deterministic by mocking tooltips

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for activating custom helm repository in preferences

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update snapshots

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove old implementation made redundant with competition for preferences of helm repositories

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add success notification for activating custom helm repository

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce way to get single active helm repository

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract responsibilities from god-class and switch to getting helm repositories using competition instead of another god class

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove dead code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add TODO

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak position of spinner

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Start handling errors when accessing helm repositories

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Handle error about no helm repositories when updating repositories

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove unwarranted function configuration

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing global overrides

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove duplication how to acquire binary path for helm

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant comment

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate naming to match Helm's internal

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Relocate file closer to it's relatives

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne SavolainenandMikko Aspiala authored and GitHub committed 2022-06-13 11:42:53 +03:00
1 parent 5420780ae0
commit 1393cc303d
99 files changed
+21409 -1039

No files matched your search

@@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import getActiveHelmRepositoriesChannelInjectable from "../../../../../common/helm/get-active-helm-repositories-channel.injectable";
import { requestFromChannelInjectionToken } from "../../../../../common/utils/channel/request-from-channel-injection-token";
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
import helmRepositoriesErrorStateInjectable from "./helm-repositories-error-state.injectable";
import { runInAction } from "mobx";
const activeHelmRepositoriesInjectable = getInjectable({
id: "active-helm-repositories",
instantiate: (di) => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
const getHelmRepositoriesChannel = di.inject(getActiveHelmRepositoriesChannelInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const helmRepositoriesErrorState = di.inject(helmRepositoriesErrorStateInjectable);
return asyncComputed(async () => {
const result = await requestFromChannel(getHelmRepositoriesChannel);
if (result.callWasSuccessful) {
return result.response;
} else {
showErrorNotification(result.error);
runInAction(() =>
helmRepositoriesErrorState.set({
controlsAreShown: false,
errorMessage: result.error,
}),
);
return [];
}
}, []);
},
});
export default activeHelmRepositoriesInjectable;
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.AddHelmRepoDialog {
--flex-gap: #{$margin * 1.5};
.accordion {
font-weight: bold;
align-self: flex-start;
padding: 0;
&:focus:not(:active) {
box-shadow: none;
}
}
.Icon {
--color-active: black;
}
.fields-title {
display: grid;
grid-template-columns: min-content auto;
align-items: center;
gap: $margin * 0.5;
}
}
@@ -0,0 +1,152 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./add-helm-repo-dialog.scss";
import React from "react";
import { Wizard, WizardStep } from "../../../../wizard";
import { Input } from "../../../../input";
import { systemName, isUrl } from "../../../../input/input_validators";
import { withInjectables } from "@ogre-tools/injectable-react";
import customHelmRepoInjectable from "./custom-helm-repo.injectable";
import type { HelmRepo } from "../../../../../../common/helm/helm-repo";
import { observer } from "mobx-react";
import type { IObservableValue } from "mobx";
import { action } from "mobx";
import submitCustomHelmRepositoryInjectable from "./submit-custom-helm-repository.injectable";
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
import { Button } from "../../../../button";
import { Icon } from "../../../../icon";
import maximalCustomHelmRepoOptionsAreShownInjectable from "./maximal-custom-helm-repo-options-are-shown.injectable";
import { SubTitle } from "../../../../layout/sub-title";
import { Checkbox } from "../../../../checkbox";
import { HelmFileInput } from "./helm-file-input/helm-file-input";
interface Dependencies {
helmRepo: HelmRepo;
hideDialog: () => void;
submitCustomRepository: (repository: HelmRepo) => Promise<void>;
maximalOptionsAreShown: IObservableValue<boolean>;
}
const NonInjectedActivationOfCustomHelmRepositoryDialogContent = observer(({ helmRepo, submitCustomRepository, maximalOptionsAreShown, hideDialog } : Dependencies) => (
<Wizard header={<h5>Add custom Helm Repo</h5>} done={hideDialog}>
<WizardStep
contentClass="flow column"
nextLabel="Add"
next={() => submitCustomRepository(helmRepo)}
testIdForNext="custom-helm-repository-submit-button"
testIdForPrev="custom-helm-repository-cancel-button"
>
<div className="flex column gaps" data-testid="add-custom-helm-repository-dialog">
<Input
autoFocus
required
placeholder="Helm repo name"
trim
validators={systemName}
value={helmRepo.name}
onChange={action(v => helmRepo.name = v)}
data-testid="custom-helm-repository-name-input"
/>
<Input
required
placeholder="URL"
validators={isUrl}
value={helmRepo.url}
onChange={action(v => helmRepo.url = v)}
data-testid="custom-helm-repository-url-input"
/>
<Button
plain
className="accordion"
data-testid="toggle-maximal-options-for-custom-helm-repository-button"
onClick={action(() => maximalOptionsAreShown.set(!maximalOptionsAreShown.get()))}
>
More
<Icon
small
tooltip="More"
material={maximalOptionsAreShown.get() ? "remove" : "add"}
/>
</Button>
{maximalOptionsAreShown.get() && (
<div data-testid="maximal-options-for-custom-helm-repository-dialog">
<SubTitle title="Security settings" />
<Checkbox
label="Skip TLS certificate checks for the repository"
value={helmRepo.insecureSkipTlsVerify}
onChange={action(v => {
helmRepo.insecureSkipTlsVerify = v;
})}
data-testid="custom-helm-repository-verify-tls-input"
/>
<HelmFileInput
placeholder="Key file"
value={helmRepo.keyFile || ""}
setValue={action((value) => helmRepo.keyFile = value)}
fileExtensions={keyExtensions}
data-testid="custom-helm-repository-key-file-input"
/>
<HelmFileInput
placeholder="Ca file"
value={helmRepo.caFile || ""}
setValue={action((value) => helmRepo.caFile = value)}
fileExtensions={certExtensions}
data-testid="custom-helm-repository-ca-cert-file-input"
/>
<HelmFileInput
placeholder="Certificate file"
value={helmRepo.certFile || ""}
setValue={action((value) => helmRepo.certFile = value)}
fileExtensions={certExtensions}
data-testid="custom-helm-repository-cert-file-input"
/>
<SubTitle title="Chart Repository Credentials" />
<Input
placeholder="Username"
value={helmRepo.username}
onChange= {action(v => helmRepo.username = v)}
data-testid="custom-helm-repository-username-input"
/>
<Input
type="password"
placeholder="Password"
value={helmRepo.password}
onChange={action(v => helmRepo.password = v)}
data-testid="custom-helm-repository-password-input"
/>
</div>
)}
</div>
</WizardStep>
</Wizard>
));
export const AddingOfCustomHelmRepositoryDialogContent = withInjectables<Dependencies>(
NonInjectedActivationOfCustomHelmRepositoryDialogContent,
{
getProps: (di) => ({
helmRepo: di.inject(customHelmRepoInjectable),
hideDialog: di.inject(hideDialogForAddingCustomHelmRepositoryInjectable),
submitCustomRepository: di.inject(submitCustomHelmRepositoryInjectable),
maximalOptionsAreShown: di.inject(maximalCustomHelmRepoOptionsAreShownInjectable),
}),
},
);
const keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"];
const certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c", "p7s", "p12", "pfx", "pem"];
@@ -0,0 +1,47 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./add-helm-repo-dialog.scss";
import React from "react";
import { Dialog } from "../../../../dialog";
import { withInjectables } from "@ogre-tools/injectable-react";
import { AddingOfCustomHelmRepositoryDialogContent } from "./adding-of-custom-helm-repository-dialog-content";
import addingOfCustomHelmRepositoryDialogIsVisibleInjectable from "./dialog-visibility/adding-of-custom-helm-repository-dialog-is-visible.injectable";
import type { IObservableValue } from "mobx";
import { observer } from "mobx-react";
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
interface Dependencies {
contentIsVisible: IObservableValue<boolean>;
hideDialog: () => void;
}
const NonInjectedActivationOfCustomHelmRepositoryDialog = observer(({
contentIsVisible,
hideDialog,
}: Dependencies) => (
<div>
<Dialog
className="AddHelmRepoDialog"
isOpen={contentIsVisible.get()}
close={hideDialog}
>
{contentIsVisible.get() && <AddingOfCustomHelmRepositoryDialogContent />}
</Dialog>
</div>
));
export const AddingOfCustomHelmRepositoryDialog = withInjectables<Dependencies>(
NonInjectedActivationOfCustomHelmRepositoryDialog,
{
getProps: (di) => ({
contentIsVisible: di.inject(addingOfCustomHelmRepositoryDialogIsVisibleInjectable),
hideDialog: di.inject(hideDialogForAddingCustomHelmRepositoryInjectable),
}),
},
);
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import { Button } from "../../../../button";
import showDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/show-dialog-for-adding-custom-helm-repository.injectable";
interface Dependencies {
showDialog: () => void;
}
const NonInjectedActivationOfCustomHelmRepositoryOpenButton = ({ showDialog }: Dependencies) => (
<Button
primary
label="Add Custom Helm Repo"
onClick={showDialog}
data-testid="add-custom-helm-repo-button"
/>
);
export const AddingOfCustomHelmRepositoryOpenButton = withInjectables<Dependencies>(
NonInjectedActivationOfCustomHelmRepositoryOpenButton,
{
getProps: (di) => ({
showDialog: di.inject(showDialogForAddingCustomHelmRepositoryInjectable),
}),
},
);
@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { observable } from "mobx";
const customHelmRepoInjectable = getInjectable({
id: "custom-helm-repo",
instantiate: () => observable({
name: "",
url: "",
username: "",
password: "",
insecureSkipTlsVerify: false,
caFile: "",
keyFile: "",
certFile: "",
}),
lifecycle: lifecycleEnum.transient,
});
export default customHelmRepoInjectable;
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
const addingOfCustomHelmRepositoryDialogIsVisibleInjectable = getInjectable({
id: "adding-of-custom-helm-repository-dialog-is-visible",
instantiate: () => observable.box(false),
});
export default addingOfCustomHelmRepositoryDialogIsVisibleInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import addingOfCustomHelmRepositoryDialogIsVisibleInjectable from "./adding-of-custom-helm-repository-dialog-is-visible.injectable";
const hideDialogForAddingCustomHelmRepositoryInjectable = getInjectable({
id: "hide-dialog-for-adding-custom-helm-repository",
instantiate: (di) => {
const state = di.inject(addingOfCustomHelmRepositoryDialogIsVisibleInjectable);
return action(() => {
state.set(false);
});
},
});
export default hideDialogForAddingCustomHelmRepositoryInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import addingOfCustomHelmRepositoryDialogIsVisibleInjectable from "./adding-of-custom-helm-repository-dialog-is-visible.injectable";
const showDialogForAddingCustomHelmRepositoryInjectable = getInjectable({
id: "show-dialog-for-adding-custom-helm-repository",
instantiate: (di) => {
const state = di.inject(addingOfCustomHelmRepositoryDialogIsVisibleInjectable);
return action(() => {
state.set(true);
});
},
});
export default showDialogForAddingCustomHelmRepositoryInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { FileFilter } from "electron";
import { requestOpenFilePickingDialog } from "../../../../../../ipc";
const getFilePathsInjectable = getInjectable({
id: "get-file-paths",
instantiate: () => async (fileFilter: FileFilter) =>
await requestOpenFilePickingDialog({
properties: ["openFile", "showHiddenFiles"],
message: `Select file`,
buttonLabel: `Use file`,
filters: [fileFilter, { name: "Any", extensions: ["*"] }],
}),
causesSideEffects: true,
});
export default getFilePathsInjectable;
@@ -0,0 +1,74 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { InputValidator } from "../../../../../input";
import { Input } from "../../../../../input";
import { Icon } from "../../../../../icon";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import getFilePathsInjectable from "./get-file-paths.injectable";
import type { FileFilter } from "electron";
import isPathInjectable from "../../../../../input/validators/is-path.injectable";
interface HelmFileInputProps {
placeholder: string;
fileExtensions: string[];
value: string;
setValue: (value: string) => void;
"data-testid"?: string;
}
interface Dependencies {
getFilePaths: (fileFilter: FileFilter) => Promise<{ canceled: boolean; filePaths: string[] }>;
isPath: InputValidator<true>;
}
const NonInjectedHelmFileInput = ({
placeholder,
value,
setValue,
fileExtensions,
getFilePaths,
isPath,
"data-testid": testId,
}: Dependencies & HelmFileInputProps) => (
<div className="flex gaps align-center">
<Input
placeholder={placeholder}
validators={isPath}
className="box grow"
value={value}
onChange={(v) => setValue(v)}
data-testid={testId}
/>
<Icon
material="folder"
onClick={async () => {
const { canceled, filePaths } = await getFilePaths({
name: placeholder,
extensions: fileExtensions,
});
if (!canceled && filePaths.length) {
setValue(filePaths[0]);
}
}}
tooltip="Browse"
/>
</div>
);
export const HelmFileInput = withInjectables<Dependencies, HelmFileInputProps>(
NonInjectedHelmFileInput,
{
getProps: (di, props) => ({
getFilePaths: di.inject(getFilePathsInjectable),
isPath: di.inject(isPathInjectable),
...props,
}),
},
);
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
const maximalCustomHelmRepoOptionsAreShownInjectable = getInjectable({
id: "maximal-custom-helm-repo-options-are-shown",
instantiate: () => observable.box(false),
});
export default maximalCustomHelmRepoOptionsAreShownInjectable;
@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { HelmRepo } from "../../../../../../common/helm/helm-repo";
import addHelmRepositoryInjectable from "../adding-of-public-helm-repository/select-helm-repository/add-helm-repository.injectable";
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
const submitCustomHelmRepositoryInjectable = getInjectable({
id: "submit-custom-helm-repository",
instantiate: (di) => {
const addHelmRepository = di.inject(addHelmRepositoryInjectable);
const hideDialog = di.inject(hideDialogForAddingCustomHelmRepositoryInjectable);
return async (repository: HelmRepo) => {
await addHelmRepository(repository);
hideDialog();
};
},
});
export default submitCustomHelmRepositoryInjectable;
@@ -0,0 +1,80 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import publicHelmRepositoriesInjectable from "./public-helm-repositories/public-helm-repositories.injectable";
import type { HelmRepo } from "../../../../../../common/helm/helm-repo";
import type { SelectOption } from "../../../../select";
import { Select } from "../../../../select";
import { Icon } from "../../../../icon";
import { observer } from "mobx-react";
import type { SingleValue } from "react-select";
import selectHelmRepositoryInjectable from "./select-helm-repository/select-helm-repository.injectable";
import { matches } from "lodash/fp";
import activeHelmRepositoriesInjectable from "../active-helm-repositories.injectable";
interface Dependencies {
publicRepositories: IAsyncComputed<HelmRepo[]>;
activeRepositories: IAsyncComputed<HelmRepo[]>;
selectRepository: (value: SingleValue<SelectOption<HelmRepo>>) => void;
}
const NonInjectedAddingOfPublicHelmRepository = observer(({
publicRepositories,
activeRepositories,
selectRepository,
}: Dependencies) => {
const dereferencesPublicRepositories = publicRepositories.value.get();
const dereferencesActiveRepositories = activeRepositories.value.get();
const valuesAreLoading = publicRepositories.pending.get() || activeRepositories.pending.get();
const repositoryOptions = dereferencesPublicRepositories.map(repository => ({
value: repository,
label: repository.name,
isSelected: !!dereferencesActiveRepositories.find(matches({ name: repository.name })),
}));
return (
<Select
id="selection-of-active-public-helm-repository"
placeholder="Repositories"
isLoading={valuesAreLoading}
isDisabled={valuesAreLoading}
options={repositoryOptions}
onChange={selectRepository}
value={dereferencesPublicRepositories}
formatOptionLabel={formatOptionLabel}
controlShouldRenderValue={false}
className="box grow"
themeName="lens"
/>
);
});
export const AddingOfPublicHelmRepository = withInjectables<Dependencies>(
NonInjectedAddingOfPublicHelmRepository,
{
getProps: (di) => ({
publicRepositories: di.inject(publicHelmRepositoriesInjectable),
activeRepositories: di.inject(activeHelmRepositoriesInjectable),
selectRepository: di.inject(selectHelmRepositoryInjectable),
}),
},
);
const formatOptionLabel = ({ value, isSelected }: SelectOption<HelmRepo>) => (
<div className="flex gaps">
<span>{value.name}</span>
{isSelected && (
<Icon
small
material="check"
className="box right" />
)}
</div>
);
@@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { sortBy } from "lodash/fp";
import type { HelmRepo } from "../../../../../../../common/helm/helm-repo";
import { customRequestPromise } from "../../../../../../../common/request";
const callForPublicHelmRepositoriesInjectable = getInjectable({
id: "call-for-public-helm-repositories",
instantiate: () => async (): Promise<HelmRepo[]> => {
const res = await customRequestPromise({
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
json: true,
resolveWithFullResponse: true,
timeout: 10000,
});
const repositories = res.body as HelmRepo[];
return sortBy(repo => repo.name, repositories);
},
causesSideEffects: true,
});
export default callForPublicHelmRepositoriesInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import callForPublicHelmRepositoriesInjectable from "./call-for-public-helm-repositories.injectable";
const publicHelmRepositoriesInjectable = getInjectable({
id: "public-helm-repositories",
instantiate: (di) => {
const callForPublicHelmRepositories = di.inject(callForPublicHelmRepositoriesInjectable);
return asyncComputed(async () => {
return await callForPublicHelmRepositories();
}, []);
},
});
export default publicHelmRepositoriesInjectable;
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import addHelmRepositoryChannelInjectable from "../../../../../../../common/helm/add-helm-repository-channel.injectable";
import type { HelmRepo } from "../../../../../../../common/helm/helm-repo";
import { requestFromChannelInjectionToken } from "../../../../../../../common/utils/channel/request-from-channel-injection-token";
import activeHelmRepositoriesInjectable from "../../active-helm-repositories.injectable";
import showErrorNotificationInjectable from "../../../../../notifications/show-error-notification.injectable";
import showSuccessNotificationInjectable from "../../../../../notifications/show-success-notification.injectable";
const addHelmRepositoryInjectable = getInjectable({
id: "add-public-helm-repository",
instantiate: (di) => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
const addHelmRepositoryChannel = di.inject(addHelmRepositoryChannelInjectable);
const activeHelmRepositories = di.inject(activeHelmRepositoriesInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
return async (repository: HelmRepo) => {
const result = await requestFromChannel(
addHelmRepositoryChannel,
repository,
);
if (result.callWasSuccessful) {
showSuccessNotification(
`Helm repository ${repository.name} has been added.`,
);
activeHelmRepositories.invalidate();
} else {
showErrorNotification(result.error);
}
};
},
});
export default addHelmRepositoryInjectable;
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import addHelmRepositoryInjectable from "./add-helm-repository.injectable";
import type { SelectOption } from "../../../../../select";
import type { HelmRepo } from "../../../../../../../common/helm/helm-repo";
import type { SingleValue } from "react-select";
import removeHelmRepositoryInjectable from "../../remove-helm-repository.injectable";
const selectHelmRepositoryInjectable = getInjectable({
id: "select-helm-repository",
instantiate: (di) => {
const addHelmRepository = di.inject(addHelmRepositoryInjectable);
const removeHelmRepository = di.inject(removeHelmRepositoryInjectable);
return (selected: SingleValue<SelectOption<HelmRepo>>) => {
if (!selected) {
return;
}
if (!selected.isSelected) {
addHelmRepository(selected.value);
} else {
removeHelmRepository(selected.value);
}
};
},
});
export default selectHelmRepositoryInjectable;
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.repos {
@apply mt-6 flex flex-col;
}
.repoName {
font-weight: 500;
margin-bottom: 8px;
}
.repoUrl {
color: var(--textColorDimmed);
}
@@ -0,0 +1,61 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { HelmRepositories } from "./helm-repositories";
import { AddingOfPublicHelmRepository } from "./adding-of-public-helm-repository/adding-of-public-helm-repository";
import { AddingOfCustomHelmRepositoryOpenButton } from "./adding-of-custom-helm-repository/adding-of-custom-helm-repository-open-button";
import { AddingOfCustomHelmRepositoryDialog } from "./adding-of-custom-helm-repository/adding-of-custom-helm-repository-dialog";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { HelmRepositoriesErrorState } from "./helm-repositories-error-state.injectable";
import helmRepositoriesErrorStateInjectable from "./helm-repositories-error-state.injectable";
import type { IObservableValue } from "mobx";
import { observer } from "mobx-react";
import { Notice } from "../../../+extensions/notice";
interface Dependencies {
helmRepositoriesErrorState: IObservableValue<HelmRepositoriesErrorState>;
}
const NonInjectedHelmCharts = observer(
({ helmRepositoriesErrorState }: Dependencies) => {
const state = helmRepositoriesErrorState.get();
return (
<div>
{!state.controlsAreShown && (
<Notice>
<div className="flex-grow text-center">{state.errorMessage}</div>
</Notice>
)}
{state.controlsAreShown && (
<div data-testid="helm-controls">
<div className="flex gaps">
<AddingOfPublicHelmRepository />
<AddingOfCustomHelmRepositoryOpenButton />
</div>
<HelmRepositories />
<AddingOfCustomHelmRepositoryDialog />
</div>
)}
</div>
);
},
);
export const HelmCharts = withInjectables<Dependencies>(
NonInjectedHelmCharts,
{
getProps: (di) => ({
helmRepositoriesErrorState: di.inject(helmRepositoriesErrorStateInjectable),
}),
},
);
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
export type HelmRepositoriesErrorState =
| { controlsAreShown: true }
| { controlsAreShown: false; errorMessage: string };
const helmRepositoriesErrorStateInjectable = getInjectable({
id: "helm-repositories-error-state",
instantiate: () =>
observable.box<HelmRepositoriesErrorState>({ controlsAreShown: true }),
});
export default helmRepositoriesErrorStateInjectable;
@@ -0,0 +1,68 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "./helm-charts.module.scss";
import React from "react";
import { observer } from "mobx-react";
import activeHelmRepositoriesInjectable from "./active-helm-repositories.injectable";
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import { Spinner } from "../../../spinner";
import type { HelmRepo } from "../../../../../common/helm/helm-repo";
import { RemovableItem } from "../../removable-item";
import removeHelmRepositoryInjectable from "./remove-helm-repository.injectable";
interface Dependencies {
activeHelmRepositories: IAsyncComputed<HelmRepo[]>;
removeRepository: (repository: HelmRepo) => Promise<void>;
}
const NonInjectedActiveHelmRepositories = observer(({ activeHelmRepositories, removeRepository }: Dependencies) => {
if (activeHelmRepositories.pending.get()) {
return (
<div className={styles.repos}>
<div className="pt-5 relative">
<Spinner center data-testid="helm-repositories-are-loading" />
</div>
</div>
);
}
const repositories = activeHelmRepositories.value.get();
return (
<div className={styles.repos}>
{repositories.map((repository) => (
<RemovableItem
key={repository.name}
onRemove={() => removeRepository(repository)}
className="mt-3"
data-testid={`remove-helm-repository-${repository.name}`}
>
<div data-testid={`helm-repository-${repository.name}`} className={styles.repoName}>
{repository.name}
</div>
<div className={styles.repoUrl}>{repository.url}</div>
</RemovableItem>
))}
</div>
);
});
export const HelmRepositories = withInjectables<Dependencies>(
NonInjectedActiveHelmRepositories,
{
getProps: (di) => ({
activeHelmRepositories: di.inject(activeHelmRepositoriesInjectable),
removeRepository: di.inject(removeHelmRepositoryInjectable),
}),
},
);
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { HelmRepo } from "../../../../../common/helm/helm-repo";
import { requestFromChannelInjectionToken } from "../../../../../common/utils/channel/request-from-channel-injection-token";
import activeHelmRepositoriesInjectable from "./active-helm-repositories.injectable";
import removeHelmRepositoryChannelInjectable from "../../../../../common/helm/remove-helm-repository-channel.injectable";
const removePublicHelmRepositoryInjectable = getInjectable({
id: "remove-public-helm-repository",
instantiate: (di) => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
const removeHelmRepositoryChannel = di.inject(removeHelmRepositoryChannelInjectable);
const activeHelmRepositories = di.inject(activeHelmRepositoriesInjectable);
return async (repository: HelmRepo) => {
await requestFromChannel(removeHelmRepositoryChannel, repository);
activeHelmRepositories.invalidate();
};
},
});
export default removePublicHelmRepositoryInjectable;