mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
fix type errors, most <Select /> errors
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
d55b363df9
commit
595bb715a9
@ -88,7 +88,7 @@ const colorTheme: PreferenceDescription<string> = {
|
||||
},
|
||||
};
|
||||
|
||||
const terminalTheme: PreferenceDescription<string | undefined> = {
|
||||
const terminalTheme: PreferenceDescription<string> = {
|
||||
fromStore(val) {
|
||||
return val || "";
|
||||
},
|
||||
|
||||
@ -11,6 +11,12 @@ export function fromEntries<T, Key extends string>(entries: Iterable<readonly [K
|
||||
return Object.fromEntries(entries) as Record<Key, T>;
|
||||
}
|
||||
|
||||
export function keys<K extends keyof any>(obj: Partial<Record<K, any>>): K[];
|
||||
|
||||
export function keys<K extends keyof any>(obj: Record<K, any>): K[] {
|
||||
return Object.keys(obj) as K[];
|
||||
}
|
||||
|
||||
export function entries<K extends string | number | symbol, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
|
||||
export function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][];
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ const clusterStub = {
|
||||
} as unknown as Cluster;
|
||||
|
||||
describe("ContextHandler", () => {
|
||||
let createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
||||
let createContextHandler: (cluster: Cluster) => ClusterContextHandler | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
@ -19,12 +19,13 @@ import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { parse } from "url";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import assert from "assert";
|
||||
|
||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||
|
||||
describe("kubeconfig manager tests", () => {
|
||||
let clusterFake: Cluster;
|
||||
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
|
||||
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager | undefined;
|
||||
let di: DiContainer;
|
||||
let logger: jest.Mocked<Logger>;
|
||||
|
||||
@ -92,6 +93,8 @@ describe("kubeconfig manager tests", () => {
|
||||
it("should create 'temp' kube config with proxy", async () => {
|
||||
const kubeConfManager = createKubeconfigManager(clusterFake);
|
||||
|
||||
assert(kubeConfManager, "should actually create one");
|
||||
|
||||
expect(logger.error).not.toBeCalled();
|
||||
expect(await kubeConfManager.getPath()).toBe(`some-directory-for-temp${path.sep}kubeconfig-foo`);
|
||||
// this causes an intermittent "ENXIO: no such device or address, read" error
|
||||
@ -107,6 +110,8 @@ describe("kubeconfig manager tests", () => {
|
||||
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
||||
const kubeConfManager = createKubeconfigManager(clusterFake);
|
||||
|
||||
assert(kubeConfManager, "should actually create one");
|
||||
|
||||
const configPath = await kubeConfManager.getPath();
|
||||
|
||||
expect(await fse.pathExists(configPath)).toBe(true);
|
||||
|
||||
@ -4,3 +4,4 @@
|
||||
*/
|
||||
|
||||
export * from "./catalog-entity-registry";
|
||||
export * from "./entity-registry";
|
||||
|
||||
@ -51,7 +51,12 @@ export class NodeShellSession extends ShellSession {
|
||||
data: `Error occurred: ${get(error, "response.body.message", error ? String(error) : "unknown error")}`,
|
||||
});
|
||||
|
||||
throw new ShellOpenError("failed to create node pod", error);
|
||||
throw new ShellOpenError(
|
||||
"failed to create node pod",
|
||||
error instanceof Error
|
||||
? { cause: error }
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
const env = await this.getCachedShellEnv();
|
||||
|
||||
@ -23,8 +23,8 @@ import { stat } from "fs/promises";
|
||||
import { getOrInsertWith } from "../../common/utils";
|
||||
|
||||
export class ShellOpenError extends Error {
|
||||
constructor(message: string, public cause: unknown) {
|
||||
super(`${message}: ${cause}`);
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
super(`${message}`, options);
|
||||
this.name = this.constructor.name;
|
||||
Error.captureStackTrace(this);
|
||||
}
|
||||
|
||||
@ -94,20 +94,6 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private formatQuotaOptionLabel = (quota: string) => {
|
||||
const iconMaterial = this.getQuotaOptionLabelIconMaterial(quota);
|
||||
|
||||
return iconMaterial
|
||||
? (
|
||||
<span className="nobr">
|
||||
<Icon material={iconMaterial} />
|
||||
{" "}
|
||||
{quota}
|
||||
</span>
|
||||
)
|
||||
: quota;
|
||||
};
|
||||
|
||||
setQuota = () => {
|
||||
if (!this.quotaSelectValue) return;
|
||||
this.quotas[this.quotaSelectValue] = this.quotaInputValue;
|
||||
@ -194,7 +180,7 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
|
||||
placeholder="Namespace"
|
||||
themeName="light"
|
||||
className="box grow"
|
||||
onChange={value => this.namespace = value}
|
||||
onChange={option => this.namespace = option?.namespace ?? null}
|
||||
/>
|
||||
|
||||
<SubTitle title="Values" />
|
||||
@ -204,11 +190,27 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
|
||||
className="quota-select"
|
||||
themeName="light"
|
||||
placeholder="Select a quota.."
|
||||
options={Object.keys(this.quotas)}
|
||||
options={Object.keys(this.quotas).map(quota => ({ quota }))}
|
||||
isMulti={false}
|
||||
value={this.quotaSelectValue}
|
||||
onChange={value => this.quotaSelectValue = value}
|
||||
formatOptionLabel={this.formatQuotaOptionLabel}
|
||||
value={(
|
||||
this.quotaSelectValue
|
||||
? ({ quota: this.quotaSelectValue })
|
||||
: null
|
||||
)}
|
||||
onChange={option => this.quotaSelectValue = option?.quota ?? null}
|
||||
formatOptionLabel={({ quota }) => {
|
||||
const iconMaterial = this.getQuotaOptionLabelIconMaterial(quota);
|
||||
|
||||
return iconMaterial
|
||||
? (
|
||||
<span className="nobr">
|
||||
<Icon material={iconMaterial} />
|
||||
{" "}
|
||||
{quota}
|
||||
</span>
|
||||
)
|
||||
: quota;
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
maxLength={10}
|
||||
|
||||
@ -18,7 +18,7 @@ import { SubTitle } from "../layout/sub-title";
|
||||
import { NamespaceSelect } from "../+namespaces/namespace-select";
|
||||
import { Select } from "../select";
|
||||
import { Icon } from "../icon";
|
||||
import { base64, iter } from "../../utils";
|
||||
import { base64, iter, object } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import upperFirst from "lodash/upperFirst";
|
||||
import { showDetails } from "../kube-detail-params";
|
||||
@ -27,20 +27,20 @@ import { fromEntries } from "../../../common/utils/objects";
|
||||
export interface AddSecretDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
interface ISecretTemplateField {
|
||||
interface SecretTemplateField {
|
||||
key: string;
|
||||
value?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface ISecretTemplate {
|
||||
[field: string]: ISecretTemplateField[] | undefined;
|
||||
annotations?: ISecretTemplateField[];
|
||||
labels?: ISecretTemplateField[];
|
||||
data?: ISecretTemplateField[];
|
||||
interface SecretTemplate {
|
||||
[field: string]: SecretTemplateField[] | undefined;
|
||||
annotations?: SecretTemplateField[];
|
||||
labels?: SecretTemplateField[];
|
||||
data?: SecretTemplateField[];
|
||||
}
|
||||
|
||||
type ISecretField = keyof ISecretTemplate;
|
||||
type ISecretField = keyof SecretTemplate;
|
||||
|
||||
const dialogState = observable.object({
|
||||
isOpen: false,
|
||||
@ -61,7 +61,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
dialogState.isOpen = false;
|
||||
}
|
||||
|
||||
private secretTemplate: Partial<Record<SecretType, ISecretTemplate>> = {
|
||||
private secretTemplate: Partial<Record<SecretType, SecretTemplate>> = {
|
||||
[SecretType.Opaque]: {},
|
||||
[SecretType.ServiceAccountToken]: {
|
||||
annotations: [
|
||||
@ -71,8 +71,8 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
},
|
||||
};
|
||||
|
||||
get types() {
|
||||
return Object.keys(this.secretTemplate) as SecretType[];
|
||||
private get secretTypeOptions() {
|
||||
return object.keys(this.secretTemplate).map(type => ({ type }));
|
||||
}
|
||||
|
||||
@observable secret = this.secretTemplate;
|
||||
@ -89,7 +89,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
AddSecretDialog.close();
|
||||
};
|
||||
|
||||
private getDataFromFields = (fields: ISecretTemplateField[] = [], processValue: (val: string) => string = (val => val)) => {
|
||||
private getDataFromFields = (fields: SecretTemplateField[] = [], processValue: (val: string) => string = (val => val)) => {
|
||||
return iter.pipeline(fields)
|
||||
.filterMap(({ key, value }) => (
|
||||
value
|
||||
@ -160,7 +160,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
tabIndex={item.required ? -1 : 0}
|
||||
readOnly={item.required}
|
||||
value={item.key}
|
||||
onChange={v => item.key = v}
|
||||
onChange={v => item.key = v}
|
||||
/>
|
||||
<Input
|
||||
multiLine
|
||||
@ -169,7 +169,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
className="value"
|
||||
placeholder="Value"
|
||||
value={item.value}
|
||||
onChange={v => item.value = v}
|
||||
onChange={v => item.value = v}
|
||||
/>
|
||||
<Icon
|
||||
small
|
||||
@ -177,7 +177,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
tooltip={item.required ? "Required field" : "Remove field"}
|
||||
className="remove-icon"
|
||||
material="remove_circle_outline"
|
||||
onClick={() => this.removeField(field, index)}
|
||||
onClick={() => this.removeField(field, index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@ -224,7 +224,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
id="secret-namespace-input"
|
||||
themeName="light"
|
||||
value={namespace}
|
||||
onChange={value => this.namespace = value ?? "default"}
|
||||
onChange={value => this.namespace = value?.namespace ?? "default"}
|
||||
/>
|
||||
</div>
|
||||
<div className="secret-type">
|
||||
@ -232,9 +232,9 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
<Select
|
||||
id="secret-input"
|
||||
themeName="light"
|
||||
options={this.types}
|
||||
value={type}
|
||||
onChange={value => this.type = value ?? SecretType.Opaque}
|
||||
options={this.secretTypeOptions}
|
||||
value={({ type })}
|
||||
onChange={value => this.type = value?.type ?? SecretType.Opaque}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -188,7 +188,7 @@ class NonInjectedEvents extends React.Component<Dependencies & EventsProps> {
|
||||
return [
|
||||
type, // type of event: "Normal" or "Warning"
|
||||
{
|
||||
className: { warning: isWarning },
|
||||
className: cssNames({ warning: isWarning }),
|
||||
title: (
|
||||
<>
|
||||
<span id={tooltipId}>{message}</span>
|
||||
@ -209,9 +209,7 @@ class NonInjectedEvents extends React.Component<Dependencies & EventsProps> {
|
||||
event.getSource(),
|
||||
event.count,
|
||||
<KubeObjectAge key="age" object={event} />,
|
||||
event.lastTimestamp
|
||||
? <ReactiveDuration key="last-seen" timestamp={event.lastTimestamp} />
|
||||
: "<unknown>",
|
||||
<ReactiveDuration key="last-seen" timestamp={event.lastTimestamp} />,
|
||||
];
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -31,9 +31,21 @@ interface Dependencies {
|
||||
themeStore: ThemeStore;
|
||||
}
|
||||
|
||||
const timezoneOptions = moment.tz.names().map(timezone => ({ timezone }));
|
||||
|
||||
const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, userStore, themeStore }) => {
|
||||
const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || "");
|
||||
const extensionSettings = appPreferenceItems.get().filter((preference) => preference.showInPreferencesTab === "application");
|
||||
const themeOptions = [
|
||||
"system",
|
||||
...themeStore.themes.keys(),
|
||||
].map(theme => ({ theme }));
|
||||
const extensionInstallRegistryOptions = ([
|
||||
"default",
|
||||
"npmrc",
|
||||
"custom",
|
||||
] as const).map(registry => ({ registry }));
|
||||
const updateChannelOptions = [...updateChannels.keys()].map(channel => ({ channel }));
|
||||
|
||||
return (
|
||||
<Preferences data-testid="application-preferences-page">
|
||||
@ -42,13 +54,10 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, us
|
||||
<section id="appearance">
|
||||
<SubTitle title="Theme" />
|
||||
<Select
|
||||
options={[
|
||||
"system",
|
||||
...themeStore.themes.keys(),
|
||||
]}
|
||||
getOptionLabel={option => themeStore.themes.get(option)?.name ?? "Sync with computer"}
|
||||
value={userStore.colorTheme}
|
||||
onChange={value => userStore.colorTheme = value ?? defaultTheme}
|
||||
options={themeOptions}
|
||||
getOptionLabel={option => themeStore.themes.get(option.theme)?.name ?? "Sync with computer"}
|
||||
value={({ theme: userStore.colorTheme })}
|
||||
onChange={value => userStore.colorTheme = value?.theme ?? defaultTheme}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
@ -58,14 +67,10 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, us
|
||||
<section id="extensionRegistryUrl">
|
||||
<SubTitle title="Extension Install Registry" />
|
||||
<Select
|
||||
options={[
|
||||
"default",
|
||||
"npmrc",
|
||||
"custom",
|
||||
]}
|
||||
value={userStore.extensionRegistryUrl.location}
|
||||
options={extensionInstallRegistryOptions}
|
||||
value={({ registry: userStore.extensionRegistryUrl.location })}
|
||||
getOptionLabel={option => {
|
||||
switch (option) {
|
||||
switch (option.registry) {
|
||||
case "custom":
|
||||
return "Custom Url";
|
||||
case "default":
|
||||
@ -75,7 +80,7 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, us
|
||||
}
|
||||
}}
|
||||
onChange={value => runInAction(() => {
|
||||
userStore.extensionRegistryUrl.location = value ?? defaultExtensionRegistryUrlLocation;
|
||||
userStore.extensionRegistryUrl.location = value?.registry ?? defaultExtensionRegistryUrlLocation;
|
||||
|
||||
if (userStore.extensionRegistryUrl.location === "custom") {
|
||||
userStore.extensionRegistryUrl.customUrl = "";
|
||||
@ -123,9 +128,9 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, us
|
||||
<section id="update-channel">
|
||||
<SubTitle title="Update Channel" />
|
||||
<Select
|
||||
options={[...updateChannels.keys()]}
|
||||
value={userStore.updateChannel}
|
||||
onChange={value => userStore.updateChannel = value ?? defaultUpdateChannel}
|
||||
options={updateChannelOptions}
|
||||
value={({ channel: userStore.updateChannel })}
|
||||
onChange={value => userStore.updateChannel = value?.channel ?? defaultUpdateChannel}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
@ -135,10 +140,10 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, us
|
||||
<section id="locale">
|
||||
<SubTitle title="Locale Timezone" />
|
||||
<Select
|
||||
options={moment.tz.names()}
|
||||
value={userStore.localeTimezone}
|
||||
getOptionLabel={timeZone => moment.tz.zone(timeZone)?.name ?? "<unknown>"}
|
||||
onChange={value => userStore.localeTimezone = value ?? defaultLocaleTimezone}
|
||||
options={timezoneOptions}
|
||||
value={({ timezone: userStore.localeTimezone })}
|
||||
getOptionLabel={option => option.timezone}
|
||||
onChange={value => userStore.localeTimezone = value?.timezone ?? defaultLocaleTimezone}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -22,6 +22,14 @@ interface Dependencies {
|
||||
|
||||
const NonInjectedEditor = observer(({ userStore }: Dependencies) => {
|
||||
const editorConfiguration = userStore.editorConfiguration;
|
||||
const minimapPositionOptions = (["left", "right"] as const)
|
||||
.map(side => ({ side }));
|
||||
const lineNumberOptions = ([
|
||||
"on",
|
||||
"off",
|
||||
"relative",
|
||||
"interval",
|
||||
] as const).map(lineNumbers => ({ lineNumbers }));
|
||||
|
||||
return (
|
||||
<Preferences data-testid="editor-preferences-page">
|
||||
@ -43,9 +51,9 @@ const NonInjectedEditor = observer(({ userStore }: Dependencies) => {
|
||||
<SubHeader compact>Position</SubHeader>
|
||||
<Select
|
||||
themeName="lens"
|
||||
options={["left", "right"]}
|
||||
value={editorConfiguration.minimap.side}
|
||||
onChange={value => editorConfiguration.minimap.side = value ?? undefined}
|
||||
options={minimapPositionOptions}
|
||||
value={editorConfiguration.minimap.side ? ({ side: editorConfiguration.minimap.side }) : null}
|
||||
onChange={value => editorConfiguration.minimap.side = value?.side}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -54,15 +62,10 @@ const NonInjectedEditor = observer(({ userStore }: Dependencies) => {
|
||||
<section>
|
||||
<SubTitle title="Line numbers"/>
|
||||
<Select
|
||||
options={[
|
||||
"on",
|
||||
"off",
|
||||
"relative",
|
||||
"interval",
|
||||
]}
|
||||
getOptionLabel={string.uppercaseFirst}
|
||||
value={editorConfiguration.lineNumbers}
|
||||
onChange={value => editorConfiguration.lineNumbers = value ?? defaultEditorConfig.lineNumbers}
|
||||
options={lineNumberOptions}
|
||||
getOptionLabel={option => string.uppercaseFirst(option.lineNumbers)}
|
||||
value={{ lineNumbers: editorConfiguration.lineNumbers }}
|
||||
onChange={value => editorConfiguration.lineNumbers = value?.lineNumbers ?? defaultEditorConfig.lineNumbers}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -30,6 +30,7 @@ const NonInjectedKubectlBinaries= observer(({
|
||||
const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || "");
|
||||
const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || "");
|
||||
const pathValidator = downloadPath ? InputValidators.isPath : undefined;
|
||||
const downloadMirrorOptions = [...packageMirrors].map(([name, mirror]) => ({ name, mirror }));
|
||||
|
||||
const save = () => {
|
||||
userStore.downloadBinariesPath = downloadPath;
|
||||
@ -52,12 +53,12 @@ const NonInjectedKubectlBinaries= observer(({
|
||||
<SubTitle title="Download mirror" />
|
||||
<Select
|
||||
placeholder="Download mirror for kubectl"
|
||||
options={[...packageMirrors.keys()]}
|
||||
value={userStore.downloadMirror}
|
||||
onChange={value => userStore.downloadMirror = value ?? defaultPackageMirror}
|
||||
getOptionLabel={mirrorKey => packageMirrors.get(mirrorKey)?.label ?? "<unknown>"}
|
||||
options={downloadMirrorOptions}
|
||||
value={downloadMirrorOptions.find(opt => opt.name === userStore.downloadMirror)}
|
||||
onChange={option => userStore.downloadMirror = option?.name ?? defaultPackageMirror}
|
||||
getOptionLabel={option => option.mirror.label}
|
||||
isDisabled={!userStore.downloadKubectlBinaries}
|
||||
isOptionDisabled={mirrorKey => !packageMirrors.get(mirrorKey)?.platforms.has(process.platform)}
|
||||
isOptionDisabled={option => option.mirror.platforms.has(process.platform)}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -24,6 +24,11 @@ interface Dependencies {
|
||||
}
|
||||
|
||||
const NonInjectedTerminal = observer(({ userStore, themeStore, defaultShell }: Dependencies) => {
|
||||
const themeOptions = [
|
||||
"",
|
||||
...themeStore.themes.keys(),
|
||||
].map(name => ({ name }));
|
||||
|
||||
return (
|
||||
<Preferences data-testid="terminal-preferences-page">
|
||||
<section>
|
||||
@ -53,12 +58,9 @@ const NonInjectedTerminal = observer(({ userStore, themeStore, defaultShell }: D
|
||||
<SubTitle title="Terminal theme" />
|
||||
<Select
|
||||
themeName="lens"
|
||||
options={[
|
||||
"",
|
||||
...themeStore.themes.keys(),
|
||||
]}
|
||||
getOptionLabel={themeName => {
|
||||
const theme = themeStore.themes.get(themeName);
|
||||
options={themeOptions}
|
||||
getOptionLabel={option => {
|
||||
const theme = themeStore.themes.get(option.name);
|
||||
|
||||
if (theme) {
|
||||
return theme.name;
|
||||
@ -66,8 +68,8 @@ const NonInjectedTerminal = observer(({ userStore, themeStore, defaultShell }: D
|
||||
|
||||
return "Match System Theme";
|
||||
}}
|
||||
value={userStore.terminalTheme}
|
||||
onChange={value => userStore.terminalTheme = value ?? ""}
|
||||
value={{ name: userStore.terminalTheme }}
|
||||
onChange={option => userStore.terminalTheme = option?.name ?? ""}
|
||||
/>
|
||||
</section>
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@ import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { ClusterRoleBindingDialog } from "./dialog";
|
||||
import { clusterRoleBindingStore } from "./legacy-store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import { clusterRoleStore } from "../+cluster-roles/legacy-store";
|
||||
import { serviceAccountStore } from "../+service-accounts/legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@ -33,7 +33,7 @@ export class ClusterRoleBindings extends React.Component {
|
||||
tableId="access_cluster_role_bindings"
|
||||
className="ClusterRoleBindings"
|
||||
store={clusterRoleBindingStore}
|
||||
dependentStores={[clusterRolesStore, serviceAccountsStore]}
|
||||
dependentStores={[clusterRoleStore, serviceAccountStore]}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: binding => binding.getName(),
|
||||
[columnId.bindings]: binding => binding.getSubjectNames(),
|
||||
|
||||
@ -10,9 +10,9 @@ import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { RoleBindingDialog } from "./dialog";
|
||||
import { roleBindingStore } from "./store";
|
||||
import { rolesStore } from "../+roles/store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import { roleStore } from "../+roles/legacy-store";
|
||||
import { clusterRoleStore } from "../+cluster-roles/legacy-store";
|
||||
import { serviceAccountStore } from "../+service-accounts/legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@ -33,7 +33,7 @@ export class RoleBindings extends React.Component {
|
||||
tableId="access_role_bindings"
|
||||
className="RoleBindings"
|
||||
store={roleBindingStore}
|
||||
dependentStores={[rolesStore, clusterRolesStore, serviceAccountsStore]}
|
||||
dependentStores={[roleStore, clusterRoleStore, serviceAccountStore]}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: binding => binding.getName(),
|
||||
[columnId.namespace]: binding => binding.getNs(),
|
||||
|
||||
@ -17,7 +17,7 @@ import { showDetails } from "../../kube-detail-params";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { rolesStore } from "./store";
|
||||
import { roleStore } from "./legacy-store";
|
||||
|
||||
export interface AddRoleDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@ -49,7 +49,7 @@ export class AddRoleDialog extends React.Component<AddRoleDialogProps> {
|
||||
|
||||
createRole = async () => {
|
||||
try {
|
||||
const role = await rolesStore.create({ name: this.roleName, namespace: this.namespace });
|
||||
const role = await roleStore.create({ name: this.roleName, namespace: this.namespace });
|
||||
|
||||
showDetails(role.selfLink);
|
||||
this.reset();
|
||||
@ -90,7 +90,7 @@ export class AddRoleDialog extends React.Component<AddRoleDialogProps> {
|
||||
id="add-dialog-namespace-select-input"
|
||||
themeName="light"
|
||||
value={this.namespace}
|
||||
onChange={namespace => this.namespace = namespace ?? "default"}
|
||||
onChange={option => this.namespace = option?.namespace ?? "default"}
|
||||
/>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
|
||||
@ -7,7 +7,8 @@ import "./welcome.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import Carousel from "react-material-ui-carousel";
|
||||
import type { CarouselProps } from "react-material-ui-carousel";
|
||||
import LegacyCarousel from "react-material-ui-carousel";
|
||||
import { Icon } from "../icon";
|
||||
import { productName, slackUrl } from "../../../common/vars";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
@ -18,6 +19,9 @@ import type { WelcomeBannerRegistration } from "./welcome-banner-items/welcome-b
|
||||
|
||||
export const defaultWidth = 320;
|
||||
|
||||
// This is to fix some react 18 type errors
|
||||
const Carousel = LegacyCarousel as React.ComponentType<CarouselProps>;
|
||||
|
||||
interface Dependencies {
|
||||
welcomeMenuItems: IComputedValue<WelcomeMenuRegistration[]>;
|
||||
welcomeBannerItems: IComputedValue<WelcomeBannerRegistration[]>;
|
||||
|
||||
@ -10,11 +10,11 @@ import capitalize from "lodash/capitalize";
|
||||
import { observer } from "mobx-react";
|
||||
import type { DatasetTooltipLabel, PieChartData } from "../chart";
|
||||
import { PieChart } from "../chart";
|
||||
import { cssVar } from "../../utils";
|
||||
import { cssVar, object } from "../../utils";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
|
||||
export interface OverviewWorkloadStatusProps {
|
||||
status: Record<string, number>;
|
||||
status: Partial<Record<string, number>>;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -32,7 +32,7 @@ export class OverviewWorkloadStatus extends React.Component<OverviewWorkloadStat
|
||||
datasets: [],
|
||||
};
|
||||
|
||||
const statuses = Object.entries(this.props.status).filter(([, val]) => val > 0);
|
||||
const statuses = object.entries(this.props.status).filter(([, val]) => val > 0);
|
||||
|
||||
if (statuses.length === 0) {
|
||||
chartData.datasets.push({
|
||||
|
||||
@ -10,7 +10,7 @@ import { Icon } from "../../icon/icon";
|
||||
import { Button } from "../../button/button";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
import { observable, reaction, makeObservable } from "mobx";
|
||||
import { observable, reaction, makeObservable, runInAction } from "mobx";
|
||||
import { ClusterMetricsResourceType } from "../../../../common/cluster-types";
|
||||
|
||||
export interface ClusterMetricsSettingProps {
|
||||
@ -40,21 +40,8 @@ export class ClusterMetricsSetting extends React.Component<ClusterMetricsSetting
|
||||
this.props.cluster.preferences.hiddenMetrics = Array.from(this.hiddenMetrics);
|
||||
};
|
||||
|
||||
onChangeSelect = (values: readonly ClusterMetricsResourceType[]) => {
|
||||
for (const value of values) {
|
||||
if (this.hiddenMetrics.has(value)) {
|
||||
this.hiddenMetrics.delete(value);
|
||||
} else {
|
||||
this.hiddenMetrics.add(value);
|
||||
}
|
||||
}
|
||||
this.save();
|
||||
};
|
||||
|
||||
onChangeButton = () => {
|
||||
Object.keys(ClusterMetricsResourceType).map(value =>
|
||||
this.hiddenMetrics.add(value),
|
||||
);
|
||||
this.hiddenMetrics.replace(Object.keys(ClusterMetricsResourceType));
|
||||
this.save();
|
||||
};
|
||||
|
||||
@ -63,20 +50,9 @@ export class ClusterMetricsSetting extends React.Component<ClusterMetricsSetting
|
||||
this.save();
|
||||
};
|
||||
|
||||
formatOptionLabel = (resource: ClusterMetricsResourceType) => (
|
||||
<div className="flex gaps align-center">
|
||||
<span>{resource}</span>
|
||||
{this.hiddenMetrics.has(resource) && (
|
||||
<Icon
|
||||
smallest
|
||||
material="check"
|
||||
className="box right"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
renderMetricsSelect() {
|
||||
const metricResourceTypeOptions = Object.values(ClusterMetricsResourceType)
|
||||
.map(type => ({ type }));
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -89,9 +65,26 @@ export class ClusterMetricsSetting extends React.Component<ClusterMetricsSetting
|
||||
onMenuClose={this.save}
|
||||
closeMenuOnSelect={false}
|
||||
controlShouldRenderValue={false}
|
||||
options={Object.values(ClusterMetricsResourceType)}
|
||||
onChange={this.onChangeSelect}
|
||||
formatOptionLabel={this.formatOptionLabel}
|
||||
options={metricResourceTypeOptions}
|
||||
onChange={(options) => {
|
||||
runInAction(() => {
|
||||
this.hiddenMetrics.replace(options.map(opt => opt.type));
|
||||
this.save();
|
||||
});
|
||||
}}
|
||||
getOptionLabel={option => option.type}
|
||||
formatOptionLabel={(option) => (
|
||||
<div className="flex gaps align-center">
|
||||
<span>{option.type}</span>
|
||||
{this.hiddenMetrics.has(option.type) && (
|
||||
<Icon
|
||||
smallest
|
||||
material="check"
|
||||
className="box right"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
themeName="lens"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@ -22,20 +22,23 @@ export interface ClusterPrometheusSettingProps {
|
||||
|
||||
const autoDetectPrometheus = Symbol("auto-detect-prometheus");
|
||||
|
||||
type ProviderOption = typeof autoDetectPrometheus | string;
|
||||
interface ProviderOption {
|
||||
provider: typeof autoDetectPrometheus | string;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterPrometheusSetting extends React.Component<ClusterPrometheusSettingProps> {
|
||||
@observable path = "";
|
||||
@observable provider: ProviderOption = autoDetectPrometheus;
|
||||
@observable selectedOption = this.options.find(opt => opt.provider === autoDetectPrometheus);
|
||||
@observable loading = true;
|
||||
loadedOptions = observable.map<string, MetricProviderInfo>();
|
||||
|
||||
@computed get options(): ProviderOption[] {
|
||||
return [
|
||||
return ([
|
||||
autoDetectPrometheus,
|
||||
...this.loadedOptions.keys(),
|
||||
];
|
||||
] as const)
|
||||
.map(provider => ({ provider }));
|
||||
}
|
||||
|
||||
constructor(props: ClusterPrometheusSettingProps) {
|
||||
@ -44,11 +47,11 @@ export class ClusterPrometheusSetting extends React.Component<ClusterPrometheusS
|
||||
}
|
||||
|
||||
@computed get canEditPrometheusPath(): boolean {
|
||||
if (this.provider === autoDetectPrometheus) {
|
||||
if (!this.selectedOption || this.selectedOption.provider === autoDetectPrometheus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.loadedOptions.get(this.provider)?.isConfigurable ?? false;
|
||||
return this.loadedOptions.get(this.selectedOption.provider)?.isConfigurable ?? false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -65,9 +68,9 @@ export class ClusterPrometheusSetting extends React.Component<ClusterPrometheusS
|
||||
}
|
||||
|
||||
if (prometheusProvider) {
|
||||
this.provider = prometheusProvider.type;
|
||||
this.selectedOption = this.options.find(opt => opt.provider === prometheusProvider.type);
|
||||
} else {
|
||||
this.provider = "";
|
||||
this.selectedOption = undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -81,7 +84,7 @@ export class ClusterPrometheusSetting extends React.Component<ClusterPrometheusS
|
||||
}
|
||||
|
||||
parsePrometheusPath = () => {
|
||||
if (!this.provider || !this.path) {
|
||||
if (!this.selectedOption || !this.path) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = this.path.split(/\/|:/, 3);
|
||||
@ -100,8 +103,8 @@ export class ClusterPrometheusSetting extends React.Component<ClusterPrometheusS
|
||||
};
|
||||
|
||||
onSaveProvider = () => {
|
||||
this.props.cluster.preferences.prometheusProvider = typeof this.provider === "string"
|
||||
? { type: this.provider }
|
||||
this.props.cluster.preferences.prometheusProvider = typeof this.selectedOption === "string"
|
||||
? { type: this.selectedOption }
|
||||
: undefined;
|
||||
};
|
||||
|
||||
@ -121,9 +124,9 @@ export class ClusterPrometheusSetting extends React.Component<ClusterPrometheusS
|
||||
<>
|
||||
<Select<ProviderOption, false, GroupBase<ProviderOption>>
|
||||
id="cluster-prometheus-settings-input"
|
||||
value={this.provider}
|
||||
value={this.selectedOption}
|
||||
onChange={provider => {
|
||||
this.provider = provider ?? autoDetectPrometheus;
|
||||
this.selectedOption = provider ?? { provider: autoDetectPrometheus };
|
||||
this.onSaveProvider();
|
||||
}}
|
||||
options={this.options}
|
||||
|
||||
@ -18,6 +18,7 @@ import { iter } from "../../utils";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import registeredCommandsInjectable from "./registered-commands/registered-commands.injectable";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import type { SingleValue } from "react-select";
|
||||
|
||||
interface Dependencies {
|
||||
commands: IComputedValue<Map<string, RegisteredCommand>>;
|
||||
@ -28,20 +29,14 @@ interface Dependencies {
|
||||
const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeCommandOverlay }: Dependencies) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const executeAction = (commandId: string | null) => {
|
||||
if (!commandId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const command = commands.get().get(commandId);
|
||||
|
||||
if (!command) {
|
||||
const executeAction = (option: SingleValue<typeof activeCommands[number]>) => {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
closeCommandOverlay();
|
||||
command.action({
|
||||
option.action({
|
||||
entity: activeEntity,
|
||||
navigate: (url, opts = {}) => {
|
||||
const { forceRootFrame = false } = opts;
|
||||
@ -54,7 +49,7 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeComman
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[COMMAND-DIALOG] failed to execute command", command.id, error);
|
||||
console.error("[COMMAND-DIALOG] failed to execute command", option.id, error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -70,8 +65,7 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeComman
|
||||
return void console.error(`[COMMAND-DIALOG]: isActive for ${command.id} threw an error, defaulting to false`, error);
|
||||
}
|
||||
})
|
||||
.map(({ id, ...rest }) => [id, rest] as const)
|
||||
.collect(items => new Map(items));
|
||||
.collect(items => Array.from(items));
|
||||
|
||||
return (
|
||||
<Select
|
||||
@ -83,15 +77,12 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeComman
|
||||
IndicatorSeparator: null,
|
||||
}}
|
||||
menuIsOpen
|
||||
options={Array.from(activeCommands.keys())}
|
||||
getOptionLabel={commandId => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { title } = activeCommands.get(commandId)!;
|
||||
|
||||
return typeof title === "string"
|
||||
options={activeCommands}
|
||||
getOptionLabel={({ title }) => (
|
||||
typeof title === "string"
|
||||
? title
|
||||
: title(context);
|
||||
}}
|
||||
: title(context)
|
||||
)}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
data-test-id="command-palette-search"
|
||||
|
||||
@ -9,7 +9,7 @@ import React from "react";
|
||||
import { formatDuration } from "../../utils";
|
||||
|
||||
export interface ReactiveDurationProps {
|
||||
timestamp: string;
|
||||
timestamp: string | undefined;
|
||||
|
||||
/**
|
||||
* Whether the display string should prefer length over precision
|
||||
@ -34,6 +34,10 @@ function computeUpdateInterval(creationTimestampEpoch: number): number {
|
||||
}
|
||||
|
||||
export const ReactiveDuration = observer(({ timestamp, compact = true }: ReactiveDurationProps) => {
|
||||
if (!timestamp) {
|
||||
return <>{"<unknown>"}</>;
|
||||
}
|
||||
|
||||
const creationTimestamp = new Date(timestamp).getTime();
|
||||
|
||||
return (
|
||||
|
||||
@ -21,12 +21,8 @@ export interface KubeObjectAgeProps {
|
||||
}
|
||||
|
||||
export const KubeObjectAge = ({ object, compact = true }: KubeObjectAgeProps) => (
|
||||
object.metadata.creationTimestamp
|
||||
? (
|
||||
<ReactiveDuration
|
||||
timestamp={object.metadata.creationTimestamp}
|
||||
compact={compact}
|
||||
/>
|
||||
)
|
||||
: <>{"<unknown>"}</>
|
||||
<ReactiveDuration
|
||||
timestamp={object.metadata.creationTimestamp}
|
||||
compact={compact}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -5,12 +5,14 @@
|
||||
|
||||
import "./sub-header.scss";
|
||||
import React from "react";
|
||||
import type { SingleOrMany } from "../../utils";
|
||||
import { cssNames } from "../../utils";
|
||||
|
||||
export interface SubHeaderProps {
|
||||
className?: string;
|
||||
withLine?: boolean; // add bottom line
|
||||
compact?: boolean; // no extra padding around content
|
||||
children: SingleOrMany<React.ReactNode>;
|
||||
}
|
||||
|
||||
export class SubHeader extends React.Component<SubHeaderProps> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user