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