mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: Turn on @typescript-eslint/strict-boolean-expressions
- Fix some bugs about bad conditions Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
d3f45bfe49
commit
1675c56e59
165 files changed
+853
-1141
No files matched your search
@@ -125,6 +125,7 @@ module.exports = {
|
||||
"no-constant-condition": ["error", {
|
||||
"checkLoops": false,
|
||||
}],
|
||||
"no-extra-boolean-cast": "off",
|
||||
"header/header": [2, "../../license-header"],
|
||||
"react/prop-types": "off",
|
||||
"no-invalid-this": "off",
|
||||
@@ -136,6 +137,11 @@ module.exports = {
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": ["error", {
|
||||
"allowNullableString": true,
|
||||
"allowNullableBoolean": true,
|
||||
"allowNullableNumber": true,
|
||||
}],
|
||||
"no-restricted-imports": ["error", {
|
||||
"paths": [
|
||||
{
|
||||
|
||||
@@ -3,27 +3,24 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import type { AppPaths } from "./app-path-injection-token";
|
||||
|
||||
const appPathsStateInjectable = getInjectable({
|
||||
id: "app-paths-state",
|
||||
|
||||
instantiate: () => {
|
||||
let state: AppPaths;
|
||||
let state: AppPaths | undefined;
|
||||
|
||||
return {
|
||||
get: () =>{
|
||||
if (!state) {
|
||||
throw new Error("Tried to get app paths before state is setupped.");
|
||||
}
|
||||
assert(state, "Tried to get app paths before initialization");
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
set: (newState: AppPaths) => {
|
||||
if (state) {
|
||||
throw new Error("Tried to overwrite existing state of app paths.");
|
||||
}
|
||||
assert(!state, "Tried to overwrite existing state of app paths");
|
||||
|
||||
state = newState;
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import type TypedEmitter from "typed-emitter";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { once } from "lodash";
|
||||
import type { Disposer, StrictReactNode } from "@k8slens/utilities";
|
||||
import { iter } from "@k8slens/utilities";
|
||||
import { isObject, iter } from "@k8slens/utilities";
|
||||
import type { CategoryColumnRegistration, TitleCellProps } from "../../renderer/components/catalog/custom-category-columns";
|
||||
import type { Literal, Metadata } from "../cluster-types";
|
||||
|
||||
@@ -364,15 +364,15 @@ export abstract class CatalogEntity<
|
||||
constructor({ metadata, status, spec }: CatalogEntityData<Metadata, Status, Spec>) {
|
||||
makeObservable(this);
|
||||
|
||||
if (!metadata || typeof metadata !== "object") {
|
||||
if (!isObject(metadata)) {
|
||||
throw new TypeError("CatalogEntity's metadata must be a defined object");
|
||||
}
|
||||
|
||||
if (!status || typeof status !== "object") {
|
||||
if (!isObject(status)) {
|
||||
throw new TypeError("CatalogEntity's status must be a defined object");
|
||||
}
|
||||
|
||||
if (!spec || typeof spec !== "object") {
|
||||
if (!isObject(spec)) {
|
||||
throw new TypeError("CatalogEntity's spec must be a defined object");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import type { SelfSignedCert } from "selfsigned";
|
||||
|
||||
const lensProxyCertificateInjectable = getInjectable({
|
||||
id: "lens-proxy-certificate",
|
||||
instantiate: () => {
|
||||
let certState: SelfSignedCert;
|
||||
let certState: SelfSignedCert | undefined;
|
||||
const cert = {
|
||||
get: () => {
|
||||
if (!certState) {
|
||||
throw "certificate has not been set";
|
||||
}
|
||||
assert(certState, "certificate has not been set");
|
||||
|
||||
return certState;
|
||||
},
|
||||
set: (certificate: SelfSignedCert) => {
|
||||
if (certState) {
|
||||
throw "certificate has already been set";
|
||||
}
|
||||
assert(!certState, "certificate has already been set");
|
||||
|
||||
certState = certificate;
|
||||
},
|
||||
|
||||
@@ -30,12 +30,14 @@ export function ipcMainHandle(channel: string, listener: (event: Electron.IpcMai
|
||||
}
|
||||
|
||||
export async function broadcastMessage(channel: string, ...args: unknown[]): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (ipcRenderer) {
|
||||
await ipcRenderer.invoke(broadcastMainChannel, channel, ...args.map(toJS));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (!webContents) {
|
||||
return;
|
||||
}
|
||||
@@ -56,9 +58,9 @@ export async function broadcastMessage(channel: string, ...args: unknown[]): Pro
|
||||
);
|
||||
});
|
||||
|
||||
const views = webContents.getAllWebContents();
|
||||
const views = webContents.getAllWebContents() ?? [];
|
||||
|
||||
if (!views || !isArray(views) || views.length === 0) return;
|
||||
if (!isArray(views) || views.length === 0) return;
|
||||
|
||||
args = args.map(toJS);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { autorun, action, observable } from "mobx";
|
||||
import type { KubeApi } from "@k8slens/kube-api";
|
||||
import type { KubeObject, ObjectReference } from "@k8slens/kube-object";
|
||||
import { parseKubeApi, createKubeApiURL } from "@k8slens/kube-api";
|
||||
import { getOrInsertWith, iter } from "@k8slens/utilities";
|
||||
import { getOrInsertWith, isDefined, iter } from "@k8slens/utilities";
|
||||
import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -142,7 +142,7 @@ export class ApiManager {
|
||||
*/
|
||||
getStore<Store extends KubeObjectStore>(api: string | KubeApi): Store | undefined ;
|
||||
getStore(apiOrBase: string | KubeApi | undefined): KubeObjectStore | undefined {
|
||||
if (!apiOrBase) {
|
||||
if (!isDefined(apiOrBase) || apiOrBase === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class ApiManager {
|
||||
|
||||
const api = apiBase && this.getApi(apiBase);
|
||||
|
||||
if (!api) {
|
||||
if (!isDefined(api) || api === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
|
||||
import type { KubeApiOptions } from "@k8slens/kube-api";
|
||||
import { KubeApi } from "@k8slens/kube-api";
|
||||
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object";
|
||||
import { isDefined } from "@k8slens/utilities";
|
||||
|
||||
export interface CreateKubeApiForRemoteClusterConfig {
|
||||
cluster: {
|
||||
@@ -92,7 +93,7 @@ const createKubeApiForRemoteClusterInjectable = getInjectable({
|
||||
serverAddress: config.cluster.server,
|
||||
apiBase: "",
|
||||
debug: isDevelopment,
|
||||
...(token ? {
|
||||
...(isDefined(token) ? {
|
||||
getRequestOptions: async () => ({
|
||||
headers: {
|
||||
"Authorization": `Bearer ${typeof token === "function" ? await token() : token}`,
|
||||
|
||||
@@ -49,10 +49,10 @@ export function normalizeMetrics(metrics: MetricData | undefined | null, frames
|
||||
if (frames > 0) {
|
||||
// fill the gaps
|
||||
result.forEach(res => {
|
||||
if (!res.values || !res.values.length) return;
|
||||
if (!res.values.length) return;
|
||||
|
||||
let now = moment().startOf("minute").subtract(1, "minute").unix();
|
||||
let timestamp = res.values[0][0];
|
||||
let timestamp = res.values[0]?.[0] ?? 0;
|
||||
|
||||
while (timestamp <= now) {
|
||||
timestamp = moment.unix(timestamp).add(1, "minute").unix();
|
||||
@@ -63,7 +63,7 @@ export function normalizeMetrics(metrics: MetricData | undefined | null, frames
|
||||
}
|
||||
|
||||
while (res.values.length < frames) {
|
||||
const timestamp = moment.unix(res.values[0][0]).subtract(1, "minute").unix();
|
||||
const timestamp = moment.unix(res.values[0]?.[0] ?? 0).subtract(1, "minute").unix();
|
||||
|
||||
if (!res.values.find((value) => value[0] === timestamp)) {
|
||||
res.values.unshift([timestamp, "0"]);
|
||||
@@ -101,9 +101,11 @@ export function getItemMetrics<Keys extends string>(metrics: Partial<Record<Keys
|
||||
}
|
||||
const results = metrics[metric]?.data.result;
|
||||
const result = results?.find(res => Object.values(res.metric)[0] == itemName);
|
||||
const item = itemMetrics[metric];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
itemMetrics[metric]!.data.result = result ? [result] : [];
|
||||
if (item) {
|
||||
item.data.result = result ? [result] : [];
|
||||
}
|
||||
}
|
||||
|
||||
return itemMetrics;
|
||||
@@ -114,7 +116,9 @@ export function getMetricLastPoints<Keys extends string>(metrics: Partial<Record
|
||||
object.entries(metrics)
|
||||
.map(([metricName, metric]) => {
|
||||
try {
|
||||
return [metricName, +metric.data.result[0].values.slice(-1)[0][1]] as const;
|
||||
const [value = [0, "0"]] = metric.data.result[0]?.values.slice(-1) ?? [];
|
||||
|
||||
return [metricName, +value[1]] as const;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
+2
-8
@@ -48,14 +48,8 @@ const requestMetricsInjectable = getInjectable({
|
||||
function requestMetrics<Keys extends string>(query: Record<Keys, Partial<Record<string, string>>>, params?: RequestMetricsParams): Promise<Record<Keys, MetricData>>;
|
||||
async function requestMetrics(metricsQuery: string | string[] | Partial<Record<string, Partial<Record<string, string>>>>, params: RequestMetricsParams = {}): Promise<MetricData | MetricData[] | Partial<Record<string, MetricData>>> {
|
||||
const { range = 3600, step = 60, namespace } = params;
|
||||
let { start, end } = params;
|
||||
|
||||
if (!start && !end) {
|
||||
const now = getSecondsFromUnixEpoch();
|
||||
|
||||
start = now - range;
|
||||
end = now;
|
||||
}
|
||||
const now = getSecondsFromUnixEpoch();
|
||||
const { start = now - range, end = now } = params;
|
||||
|
||||
return apiBase.post("/metrics", {
|
||||
data: metricsQuery,
|
||||
|
||||
@@ -306,7 +306,7 @@ export class KubeObjectStore<
|
||||
}
|
||||
|
||||
protected resetOnError(error: unknown) {
|
||||
if (error) this.reset();
|
||||
if (Boolean(error)) this.reset();
|
||||
}
|
||||
|
||||
protected async loadItem(params: { name: string; namespace?: string }): Promise<K | null> {
|
||||
|
||||
@@ -122,7 +122,7 @@ export abstract class LensProtocolRouter {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return countBy(b.path)["/"] - countBy(a.path)["/"];
|
||||
return (countBy(b.path)["/"] ?? 0) - (countBy(a.path)["/"] ?? 0);
|
||||
})[0] ?? null;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ export abstract class LensProtocolRouter {
|
||||
const extension = extensionLoader.getInstanceByName(name) as LensExtension | undefined;
|
||||
|
||||
if (!extension) {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but does not have a class for ${ipcRenderer ? "renderer" : "main"}`);
|
||||
|
||||
return name;
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export type KubeResource =
|
||||
"namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" | "leases" |
|
||||
"secrets" | "configmaps" | "ingresses" | "ingressclasses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" |
|
||||
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "replicationcontrollers" | "jobs" | "cronjobs" |
|
||||
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "verticalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" |
|
||||
"priorityclasses" | "runtimeclasses" |
|
||||
"roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts" | "mutatingwebhookconfigurations" | "validatingwebhookconfigurations";
|
||||
export type KubeResource = keyof typeof apiResourceRecord;
|
||||
|
||||
export interface KubeApiResource {
|
||||
kind: string;
|
||||
@@ -35,7 +29,7 @@ export interface KubeApiResourceData {
|
||||
namespaced: boolean;
|
||||
}
|
||||
|
||||
export const apiResourceRecord: Record<KubeResource, KubeApiResourceData> = {
|
||||
export const apiResourceRecord = {
|
||||
clusterroles: {
|
||||
kind: "ClusterRole",
|
||||
group: "rbac.authorization.k8s.io",
|
||||
|
||||
@@ -20,15 +20,16 @@ export function reactiveNow(interval?: number | "frame") {
|
||||
|
||||
// Note: This is the kludge until https://github.com/mobxjs/mobx-utils/issues/306 is fixed
|
||||
const synchronizationIsEnabled = !process.env.JEST_WORKER_ID;
|
||||
let ticker = tickers[interval];
|
||||
|
||||
if (!tickers[interval] || !synchronizationIsEnabled) {
|
||||
if (!ticker || !synchronizationIsEnabled) {
|
||||
if (typeof interval === "number")
|
||||
tickers[interval] = createIntervalTicker(interval);
|
||||
ticker = tickers[interval] = createIntervalTicker(interval);
|
||||
else
|
||||
tickers[interval] = createAnimationFrameTicker();
|
||||
ticker = tickers[interval] = createAnimationFrameTicker();
|
||||
}
|
||||
|
||||
return tickers[interval].current();
|
||||
return ticker.current();
|
||||
}
|
||||
|
||||
function createIntervalTicker(interval: number) {
|
||||
|
||||
@@ -109,6 +109,7 @@ export class ExtensionDiscovery {
|
||||
* Initializes the class and setups the file watcher for added/removed local extensions.
|
||||
*/
|
||||
async init() {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (ipcRenderer) {
|
||||
await this.initRenderer();
|
||||
} else {
|
||||
|
||||
@@ -101,6 +101,7 @@ export class ExtensionLoader {
|
||||
));
|
||||
|
||||
async init() {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (ipcMain) {
|
||||
await this.initMain();
|
||||
} else {
|
||||
@@ -211,6 +212,7 @@ export class ExtensionLoader {
|
||||
}
|
||||
|
||||
broadcastExtensions() {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
const channel = ipcRenderer
|
||||
? extensionLoaderFromRendererChannel
|
||||
: extensionLoaderFromMainChannel;
|
||||
|
||||
@@ -145,7 +145,9 @@ export class LensRendererExtension extends LensExtension {
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
async isEnabledForCluster(cluster: KubernetesCluster): Promise<Boolean> {
|
||||
return Promise.resolve((void cluster) || true);
|
||||
void cluster;
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -146,10 +146,13 @@ export const logTabStore = Object.assign(
|
||||
createWorkloadTab: asLegacyGlobalFunctionForExtensionApi(createWorkloadLogsTabInjectable),
|
||||
renameTab: (tabId: string): void => {
|
||||
const { selectedPodId } = logTabStore.getData(tabId) ?? {};
|
||||
const pod = selectedPodId && podStore.getById(selectedPodId);
|
||||
|
||||
if (pod) {
|
||||
renameTab(tabId, `Pod ${pod.getName()}`);
|
||||
if (selectedPodId) {
|
||||
const pod = podStore.getById(selectedPodId);
|
||||
|
||||
if (pod) {
|
||||
renameTab(tabId, `Pod ${pod.getName()}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
tabs: undefined,
|
||||
|
||||
@@ -38,12 +38,13 @@ import roleBindingApiInjectable from "../../common/k8s-api/endpoints/role-bindin
|
||||
import customResourceDefinitionApiInjectable from "../../common/k8s-api/endpoints/custom-resource-definition.api.injectable";
|
||||
import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||
import requestMetricsInjectable from "../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
|
||||
import { maybeGet } from "@k8slens/utilities";
|
||||
|
||||
export function isAllowedResource(resources: KubeResource | KubeResource[]) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
return [resources].flat().every((resourceName) => {
|
||||
const resource = apiResourceRecord[resourceName];
|
||||
return [resources].flat().every((resourceName: string) => {
|
||||
const resource = maybeGet(apiResourceRecord, resourceName);
|
||||
|
||||
if (!resource) {
|
||||
return true;
|
||||
|
||||
+6
-5
@@ -81,11 +81,12 @@ const toRecursedInjectablesFor = (logError: (errorMessage: string) => void) => {
|
||||
injectionToken: applicationMenuItemInjectionToken,
|
||||
}),
|
||||
|
||||
...((registration.submenu as MenuRegistration[])
|
||||
? (registration.submenu as MenuRegistration[]).flatMap(
|
||||
toRecursedInjectables(currentIdPath),
|
||||
)
|
||||
: []),
|
||||
...(
|
||||
registration.submenu
|
||||
? (registration.submenu as MenuRegistration[])
|
||||
.flatMap(toRecursedInjectables(currentIdPath))
|
||||
: []
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
+5
-4
@@ -40,10 +40,11 @@ const NonInjectedUpdateButton = observer(({ warningLevel, update, id }: UpdateBu
|
||||
data-testid="update-button"
|
||||
data-warning-level={level}
|
||||
id={buttonId}
|
||||
className={cssNames(styles.updateButton, {
|
||||
[styles.warningHigh]: level === "high",
|
||||
[styles.warningMedium]: level === "medium",
|
||||
})}
|
||||
className={cssNames(
|
||||
styles.updateButton,
|
||||
level === "high" && styles.warningHigh,
|
||||
level === "medium" && styles.warningMedium,
|
||||
)}
|
||||
>
|
||||
Update
|
||||
<Icon material="arrow_drop_down" className={styles.icon}/>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ const selectedUpdateChannelInjectable = getInjectable({
|
||||
|
||||
setValue: action((channelId) => {
|
||||
const targetUpdateChannel =
|
||||
channelId && updateChannels[channelId]
|
||||
channelId && (channelId in updateChannels)
|
||||
? updateChannels[channelId]
|
||||
: defaultUpdateChannel;
|
||||
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { isTruthy } from "@k8slens/utilities";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { globalAgent } from "https";
|
||||
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||
@@ -34,7 +35,7 @@ const injectSystemCAsInjectable = getInjectable({
|
||||
return globalAgent.options.ca;
|
||||
}
|
||||
|
||||
if (globalAgent.options.ca) {
|
||||
if (isTruthy(globalAgent.options.ca)) {
|
||||
return [globalAgent.options.ca];
|
||||
}
|
||||
|
||||
|
||||
@@ -73,13 +73,17 @@ const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
|
||||
{
|
||||
// grab the default named hotbar or the first.
|
||||
const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default"));
|
||||
const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1);
|
||||
const [hotbar] = hotbars.splice(defaultHotbarIndex, 1);
|
||||
|
||||
workspaceHotbars.set("default", {
|
||||
name,
|
||||
id,
|
||||
items: items.filter(isDefined),
|
||||
});
|
||||
if (hotbar) {
|
||||
const { name, id, items } = hotbar;
|
||||
|
||||
workspaceHotbars.set("default", {
|
||||
name,
|
||||
id,
|
||||
items: items.filter(isDefined),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const cluster of clusters) {
|
||||
@@ -130,7 +134,8 @@ const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
|
||||
const defaultHotbarIndex = hotbars.findIndex(hotbar => hotbar.name === "default");
|
||||
|
||||
if (defaultHotbarIndex >= 0) {
|
||||
const defaultHotbar = createHotbar(hotbars[defaultHotbarIndex]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const defaultHotbar = createHotbar(hotbars[defaultHotbarIndex]!);
|
||||
|
||||
if (defaultHotbar.isFull()) {
|
||||
// making a new hotbar is less destructive if the first hotbar
|
||||
|
||||
+4
-5
@@ -113,11 +113,10 @@ const registratorForPreferenceItemsInjectable = getInjectable({
|
||||
const items = extension.appPreferences.map((registration, i) => {
|
||||
const itemId = `${commonId}-item-${registration.id ?? i}`;
|
||||
|
||||
const itemIsInSpecialTab =
|
||||
registration.showInPreferencesTab &&
|
||||
["telemetry", "application"].includes(
|
||||
registration.showInPreferencesTab,
|
||||
);
|
||||
const itemIsInSpecialTab = (
|
||||
registration.showInPreferencesTab === "telemetry"
|
||||
|| registration.showInPreferencesTab === "application"
|
||||
);
|
||||
|
||||
return getInjectable({
|
||||
id: itemId,
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import type { LensRendererExtension } from "../../../../extensions/lens-renderer
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { topBarItemOnRightSideInjectionToken } from "../../../../renderer/components/layout/top-bar/top-bar-items/top-bar-item-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
|
||||
const legacyExtensionApiRegistratorForTopBarItemsInjectable = getInjectable({
|
||||
id: "legacy-extension-api-registrator-for-top-bar-items",
|
||||
@@ -19,7 +20,7 @@ const legacyExtensionApiRegistratorForTopBarItemsInjectable = getInjectable({
|
||||
return pipeline(
|
||||
extension.topBarItems,
|
||||
|
||||
reject((registration) => !registration?.components?.Item),
|
||||
reject((registration) => !(registration as PartialDeep<typeof registration>)?.components?.Item),
|
||||
|
||||
(validTopBarRegistrations) =>
|
||||
validTopBarRegistrations.map((registration, index) => {
|
||||
|
||||
@@ -64,7 +64,7 @@ jest.mock("@k8slens/tooltip", () => ({
|
||||
void tooltipOverrideDisabled;
|
||||
const ResolvedTarget = Target as React.FunctionComponent<TargetProps>;
|
||||
|
||||
if (tooltip) {
|
||||
if (Boolean(tooltip)) {
|
||||
const testId = props["data-testid"] as string | undefined;
|
||||
|
||||
return (
|
||||
|
||||
@@ -111,9 +111,7 @@ export const createClusterPrometheusHandler = (...args: [Dependencies, Cluster])
|
||||
break;
|
||||
|
||||
case "fulfilled":
|
||||
if (res.value) {
|
||||
return res.value;
|
||||
}
|
||||
return res.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +128,10 @@ export const createClusterPrometheusHandler = (...args: [Dependencies, Cluster])
|
||||
const prometheusPath = ensurePrometheusPath(service);
|
||||
const provider = ensurePrometheusProvider(service);
|
||||
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { prometheusPath, provider };
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const clusterStoreSnapMigrationInjectable = getInjectable({
|
||||
}
|
||||
|
||||
logger.info("Migrating embedded kubeconfig paths");
|
||||
const storedClusters = (store.get("clusters") || []) as ClusterModel[];
|
||||
const storedClusters = (store.get("clusters") as (ClusterModel[] | undefined) ?? []);
|
||||
|
||||
if (!storedClusters.length) return;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import * as yaml from "js-yaml";
|
||||
import { iter, put, sortBySemverVersion } from "@k8slens/utilities";
|
||||
import { isObject, iter, put, sortBySemverVersion } from "@k8slens/utilities";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable";
|
||||
import type { Logger } from "@k8slens/logger";
|
||||
@@ -71,11 +71,11 @@ export class HelmChartManager {
|
||||
const cacheFileStats = await this.dependencies.stat(this.repo.cacheFilePath);
|
||||
const data = yaml.load(cacheFile) as string | number | HelmCacheFile;
|
||||
|
||||
if (!data || typeof data !== "object" || typeof data.entries !== "object") {
|
||||
if (!isObject(data) || !isObject(data.entries)) {
|
||||
throw Object.assign(new TypeError("Helm Cache file does not parse correctly"), { file: this.repo.cacheFilePath, data });
|
||||
}
|
||||
|
||||
const normalized = normalizeHelmCharts(this.repo.name, data.entries);
|
||||
const normalized = normalizeHelmCharts(this.repo.name, data.entries as RepoHelmChartList);
|
||||
|
||||
return put(
|
||||
this.dependencies.cache,
|
||||
|
||||
@@ -21,14 +21,17 @@ const getClusterForRequestInjectable = getInjectable({
|
||||
// lens-server is connecting to 127.0.0.1:<port>/<uid>
|
||||
if (req.url && req.headers.host.startsWith("127.0.0.1")) {
|
||||
const clusterId = req.url.split("/")[1];
|
||||
const cluster = getClusterById(clusterId);
|
||||
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
|
||||
if (clusterId) {
|
||||
const cluster = getClusterById(clusterId);
|
||||
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
|
||||
}
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
const clusterId = getClusterIdFromHost(req.headers.host);
|
||||
|
||||
@@ -200,7 +200,7 @@ export class LensProxy {
|
||||
|
||||
this.dependencies.logger.error(`[LENS-PROXY]: http proxy errored for cluster: ${String(error)}`, { url: req.url });
|
||||
|
||||
if (target) {
|
||||
if (target !== undefined) {
|
||||
this.dependencies.logger.debug(`Failed proxy to target: ${JSON.stringify(target, null, 2)}`);
|
||||
|
||||
if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) {
|
||||
@@ -245,12 +245,10 @@ export class LensProxy {
|
||||
const kubeAuthProxyServer = this.dependencies.getKubeAuthProxyServer(cluster);
|
||||
const proxyTarget = await kubeAuthProxyServer.getApiTarget(isLongRunningRequest(req.url));
|
||||
|
||||
if (proxyTarget) {
|
||||
return this.dependencies.proxy.web(req, res, proxyTarget);
|
||||
}
|
||||
this.dependencies.proxy.web(req, res, proxyTarget);
|
||||
} else {
|
||||
res.setHeader("Content-Security-Policy", this.dependencies.contentSecurityPolicy);
|
||||
await this.dependencies.router.route(cluster, req, res);
|
||||
}
|
||||
|
||||
res.setHeader("Content-Security-Policy", this.dependencies.contentSecurityPolicy);
|
||||
await this.dependencies.router.route(cluster, req, res);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -3,7 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { chunk } from "lodash";
|
||||
import type { ConnectionOptions } from "tls";
|
||||
import { connect } from "tls";
|
||||
import url from "url";
|
||||
@@ -13,6 +12,7 @@ import type { LensProxyApiRequest } from "../lens-proxy";
|
||||
import kubeAuthProxyServerInjectable from "../../cluster/kube-auth-proxy-server.injectable";
|
||||
import kubeAuthProxyCertificateInjectable from "../../kube-auth-proxy/kube-auth-proxy-certificate.injectable";
|
||||
import clusterApiUrlInjectable from "../../../features/cluster/connections/main/api-url.injectable";
|
||||
import { iter } from "@k8slens/utilities";
|
||||
|
||||
const skipRawHeaders = new Set(["Host", "Authorization"]);
|
||||
|
||||
@@ -35,7 +35,7 @@ const kubeApiUpgradeRequestInjectable = getInjectable({
|
||||
proxySocket.write(`${req.method} ${pUrl.path ?? ""} HTTP/1.1\r\n`);
|
||||
proxySocket.write(`Host: ${clusterApiUrl.host}\r\n`);
|
||||
|
||||
for (const [key, value] of chunk(req.rawHeaders, 2)) {
|
||||
for (const [key, value] of iter.chunks(req.rawHeaders, 2)) {
|
||||
if (skipRawHeaders.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { InspectOptions } from "util";
|
||||
import { inspect } from "util";
|
||||
import { omit } from "lodash";
|
||||
import type { Format, TransformableInfo } from "logform";
|
||||
import { isObject, isTruthy } from "@k8slens/utilities";
|
||||
|
||||
// The following license was copied from https://github.com/duccio/winston-console-format/blob/master/LICENSE
|
||||
// This was modified to support formatting causes
|
||||
@@ -116,9 +117,15 @@ export class ConsoleFormat implements Format {
|
||||
private _cause(source: unknown): string[] {
|
||||
const messages: string[] = [];
|
||||
|
||||
if (source instanceof Error && source.cause) {
|
||||
if (source instanceof Error && isTruthy(source.cause)) {
|
||||
messages.push("Cause:");
|
||||
messages.push(...this.getLines(omit(source.cause, "response")).map(l => ` ${l}`));
|
||||
|
||||
if (isObject(source.cause)) {
|
||||
messages.push(...this.getLines(omit(source.cause, "response")).map(l => ` ${l}`));
|
||||
} else {
|
||||
messages.push(...this.getLines(source.cause).map(l => ` ${l}`));
|
||||
}
|
||||
|
||||
messages.push(...this._cause(source.cause));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,14 @@ import { matches } from "lodash/fp";
|
||||
import type { PrometheusProvider } from "./provider";
|
||||
import prometheusProvidersInjectable from "./providers.injectable";
|
||||
|
||||
export type GetPrometheusProviderByKind = (kind: string) => PrometheusProvider;
|
||||
export type GetPrometheusProviderByKind = (kind: string) => PrometheusProvider | undefined;
|
||||
|
||||
const getPrometheusProviderByKindInjectable = getInjectable({
|
||||
id: "get-prometheus-provider-by-kind",
|
||||
instantiate: (di): GetPrometheusProviderByKind => {
|
||||
const providers = di.inject(prometheusProvidersInjectable);
|
||||
|
||||
return (kind) => {
|
||||
const provider = providers.get().find(matches({ kind }));
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(`Provider of kind "${kind}" does not exist`);
|
||||
}
|
||||
|
||||
return provider;
|
||||
};
|
||||
return (kind) => providers.get().find(matches({ kind }));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
import type { PrometheusProvider } from "./provider";
|
||||
import { createPrometheusProvider, bytesSent, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
|
||||
export const getHelmLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||
(opts, queryName) => {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
assert(opts.nodes, 'Missing "nodes" option when "category" option is "cluster"');
|
||||
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (component)`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`);
|
||||
@@ -69,6 +72,10 @@ export const getHelmLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
assert(opts.pods, 'Missing "pods" option when "category" option is "pods"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pods"');
|
||||
assert(opts.selector, 'Missing "selector" option when "category" option is "pods"');
|
||||
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||
@@ -95,6 +102,9 @@ export const getHelmLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
assert(opts.pvc, 'Missing "pvc" option when "category" option is "pvc"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pvc"');
|
||||
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
@@ -103,6 +113,9 @@ export const getHelmLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
assert(opts.ingress, 'Missing "ingress" option when "category" option is "ingress"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "ingress"');
|
||||
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return bytesSent({
|
||||
@@ -126,7 +139,7 @@ export const getHelmLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${String(opts.category)}"`);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
import { bytesSent, prometheusProviderInjectionToken, findNamespacedService, createPrometheusProvider } from "./provider";
|
||||
import type { PrometheusProvider } from "./provider";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
|
||||
export const getLensLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||
(opts, queryName) => {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
assert(opts.nodes, 'Missing "nodes" option when "category" option is "cluster"');
|
||||
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_name)`.replace(/_bytes/g, `_bytes{kubernetes_node=~"${opts.nodes}"}`);
|
||||
@@ -69,6 +72,10 @@ export const getLensLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
assert(opts.pods, 'Missing "pods" option when "category" option is "pods"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pods"');
|
||||
assert(opts.selector, 'Missing "selector" option when "category" option is "pods"');
|
||||
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||
@@ -95,6 +102,9 @@ export const getLensLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
assert(opts.pvc, 'Missing "pvc" option when "category" option is "pvc"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pvc"');
|
||||
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
@@ -103,6 +113,9 @@ export const getLensLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
assert(opts.ingress, 'Missing "ingress" option when "category" option is "ingress"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "ingress"');
|
||||
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return bytesSent({
|
||||
@@ -126,7 +139,7 @@ export const getLensLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }):
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${String(opts.category)}"`);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
import type { PrometheusProvider } from "./provider";
|
||||
import { bytesSent, createPrometheusProvider, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
|
||||
export const getOperatorLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||
(opts, queryName) => {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
assert(opts.nodes, 'Missing "nodes" option when "category" option is "cluster"');
|
||||
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes))`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`);
|
||||
@@ -69,6 +72,10 @@ export const getOperatorLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
assert(opts.pods, 'Missing "pods" option when "category" option is "pods"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pods"');
|
||||
assert(opts.selector, 'Missing "selector" option when "category" option is "pods"');
|
||||
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||
@@ -95,6 +102,9 @@ export const getOperatorLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
assert(opts.pvc, 'Missing "pvc" option when "category" option is "pvc"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pvc"');
|
||||
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}", namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
@@ -103,6 +113,9 @@ export const getOperatorLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
assert(opts.ingress, 'Missing "ingress" option when "category" option is "ingress"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "ingress"');
|
||||
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return bytesSent({
|
||||
@@ -126,7 +139,7 @@ export const getOperatorLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${String(opts.category)}"`);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
import type { PrometheusProvider } from "./provider";
|
||||
import { bytesSent, createPrometheusProvider, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
|
||||
export const getStacklightLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||
(opts, queryName) => {
|
||||
switch(opts.category) {
|
||||
case "cluster":
|
||||
assert(opts.nodes, 'Missing "nodes" option when "category" option is "cluster"');
|
||||
|
||||
switch (queryName) {
|
||||
case "memoryUsage":
|
||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_name)`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`);
|
||||
@@ -69,6 +72,10 @@ export const getStacklightLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: stri
|
||||
}
|
||||
break;
|
||||
case "pods":
|
||||
assert(opts.pods, 'Missing "pods" option when "category" option is "pods"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pods"');
|
||||
assert(opts.selector, 'Missing "selector" option when "category" option is "pods"');
|
||||
|
||||
switch (queryName) {
|
||||
case "cpuUsage":
|
||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||
@@ -95,6 +102,9 @@ export const getStacklightLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: stri
|
||||
}
|
||||
break;
|
||||
case "pvc":
|
||||
assert(opts.pvc, 'Missing "pvc" option when "category" option is "pvc"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "pvc"');
|
||||
|
||||
switch (queryName) {
|
||||
case "diskUsage":
|
||||
return `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}",namespace="${opts.namespace}"}) by (persistentvolumeclaim, namespace)`;
|
||||
@@ -103,6 +113,9 @@ export const getStacklightLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: stri
|
||||
}
|
||||
break;
|
||||
case "ingress":
|
||||
assert(opts.ingress, 'Missing "ingress" option when "category" option is "ingress"');
|
||||
assert(opts.namespace, 'Missing "namespace" option when "category" option is "ingress"');
|
||||
|
||||
switch (queryName) {
|
||||
case "bytesSentSuccess":
|
||||
return bytesSent({
|
||||
@@ -126,7 +139,7 @@ export const getStacklightLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: stri
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||
throw new Error(`Unknown queryName="${queryName}" for category="${String(opts.category)}"`);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ import type { LensExtension } from "../../../extensions/lens-extension";
|
||||
import { observable, when } from "mobx";
|
||||
import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler";
|
||||
import { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
||||
import { disposer, noop } from "@k8slens/utilities";
|
||||
import { disposer, isTruthy, noop } from "@k8slens/utilities";
|
||||
import type { BroadcastMessage } from "../../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
export interface FallbackHandler {
|
||||
@@ -79,7 +79,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
await this._routeToExtension(url);
|
||||
}
|
||||
} catch (error) {
|
||||
void this.dependencies.broadcastMessage(ProtocolHandlerInvalid, error ? String(error) : "unknown error", rawUrl);
|
||||
void this.dependencies.broadcastMessage(ProtocolHandlerInvalid, isTruthy(error) ? String(error) : "unknown error", rawUrl);
|
||||
|
||||
if (error instanceof proto.RoutingError) {
|
||||
this.dependencies.logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${String(error)}`, { url: error.url });
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { ServerResponse } from "http";
|
||||
import { loggerInjectionToken } from "@k8slens/logger";
|
||||
import { object } from "@k8slens/utilities";
|
||||
import { isDefined, object } from "@k8slens/utilities";
|
||||
import type { LensApiRequest, Route } from "./route";
|
||||
import { contentTypes } from "./router-content-types";
|
||||
|
||||
@@ -34,7 +34,7 @@ const writeServerResponseFor = (serverResponse: ServerResponse) => ({
|
||||
if (content instanceof Buffer) {
|
||||
serverResponse.write(content);
|
||||
serverResponse.end();
|
||||
} else if (content) {
|
||||
} else if (isDefined(content)) {
|
||||
serverResponse.end(content);
|
||||
} else {
|
||||
serverResponse.end();
|
||||
@@ -67,7 +67,7 @@ const createHandlerForRouteInjectable = getInjectable({
|
||||
} catch(error) {
|
||||
const mappedResult = contentTypes.txt.resultMapper({
|
||||
statusCode: 500,
|
||||
error: error ? String(error) : "unknown error",
|
||||
error: isDefined(error) ? String(error) : "unknown error",
|
||||
});
|
||||
|
||||
logger.error(`[ROUTER]: route ${route.path}, called with ${request.path}, threw an error`, error);
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface ClusterLensApiRequest<Path extends string> extends LensApiReque
|
||||
export interface LensApiResult<Response> {
|
||||
statusCode?: number;
|
||||
response?: Response;
|
||||
error?: unknown;
|
||||
error?: string | Error;
|
||||
contentType?: LensApiResultContentType;
|
||||
headers?: Partial<Record<string, string>>;
|
||||
proxy?: httpProxy;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { isDefined } from "@k8slens/utilities";
|
||||
import type { LensApiResult } from "./route";
|
||||
|
||||
export interface LensApiResultContentType {
|
||||
@@ -14,19 +15,15 @@ export interface LensApiResultContentType {
|
||||
|
||||
const resultMapperFor =
|
||||
(contentType: string): LensApiResultContentType["resultMapper"] =>
|
||||
({ response, error, statusCode= error ? 400 : 200, headers = {}}) => ({
|
||||
({ response, error, statusCode = isDefined(error) ? 200 : 400, headers = {}}) => ({
|
||||
statusCode,
|
||||
content: error || response,
|
||||
content: isDefined(error) ? error : response,
|
||||
headers: { ...headers, "Content-Type": contentType },
|
||||
});
|
||||
|
||||
export type SupportedFileExtension = "json" | "txt" | "html" | "css" | "gif" | "jpg" | "png" | "svg" | "js" | "woff2" | "ttf";
|
||||
|
||||
export interface ContentTypes extends Record<SupportedFileExtension, LensApiResultContentType> {
|
||||
[key: string]: LensApiResultContentType | undefined;
|
||||
}
|
||||
|
||||
export const contentTypes: ContentTypes = {
|
||||
export const contentTypes: Record<SupportedFileExtension, LensApiResultContentType> = {
|
||||
json: {
|
||||
resultMapper: (result: LensApiResult<unknown>) => {
|
||||
const resultMapper = resultMapperFor("application/json");
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { LensApiRequest } from "../../router/route";
|
||||
import path from "path";
|
||||
import { contentTypes } from "../../router/router-content-types";
|
||||
import { prefixedLoggerInjectable } from "@k8slens/logger";
|
||||
import { maybeGet } from "@k8slens/utilities";
|
||||
|
||||
const prodStaticFileRouteHandlerInjectable = getInjectable({
|
||||
id: "prod-static-file-route-handler",
|
||||
@@ -33,7 +34,7 @@ const prodStaticFileRouteHandlerInjectable = getInjectable({
|
||||
}
|
||||
|
||||
const fileExtension = path.extname(assetFilePath).slice(1);
|
||||
const contentType = contentTypes[fileExtension] || contentTypes.txt;
|
||||
const contentType = maybeGet(contentTypes, fileExtension) || contentTypes.txt;
|
||||
|
||||
try {
|
||||
return { response: await readFileBuffer(assetFilePath), contentType };
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.inje
|
||||
import { initialNodeShellImage } from "../../../common/cluster-types";
|
||||
import type { LoadProxyKubeconfig } from "../../cluster/load-proxy-kubeconfig.injectable";
|
||||
import type { Pod } from "@k8slens/kube-object";
|
||||
import { isDefined } from "@k8slens/utilities";
|
||||
|
||||
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||
nodeName: string;
|
||||
@@ -67,7 +68,7 @@ export class NodeShellSession extends ShellSession {
|
||||
|
||||
this.send({
|
||||
type: TerminalChannels.STDOUT,
|
||||
data: `Error occurred: ${get(error, "response.body.message", error ? String(error) : "unknown error")}`,
|
||||
data: `Error occurred: ${get(error, "response.body.message", isDefined(error) ? String(error) : "unknown error")}`,
|
||||
});
|
||||
|
||||
throw new ShellOpenError(
|
||||
|
||||
@@ -271,7 +271,7 @@ export abstract class ShellSession {
|
||||
}
|
||||
})
|
||||
.once("close", code => {
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${String(WebSocketCloseEvent[code])}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
|
||||
const stopShellSession = this.running
|
||||
&& (
|
||||
|
||||
@@ -55,15 +55,11 @@ const toItemInjectablesFor = (extension: LensMainExtension, withErrorLoggingFor:
|
||||
separator: registration.type === "separator",
|
||||
|
||||
label: computed(() => {
|
||||
if (!registration.label) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (isString(registration.label)) {
|
||||
return registration.label;
|
||||
}
|
||||
|
||||
return registration.label.get();
|
||||
return registration.label?.get() ?? "";
|
||||
}),
|
||||
|
||||
tooltip: registration.toolTip,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { computed, observable, makeObservable, action, runInAction } from "mobx"
|
||||
import { ipcRendererOn } from "../../../../common/ipc";
|
||||
import type { CatalogCategory, CatalogEntity, CatalogEntityData, CatalogCategoryRegistry, CatalogEntityKindData } from "../../../../common/catalog";
|
||||
import type { Disposer } from "@k8slens/utilities";
|
||||
import { iter } from "@k8slens/utilities";
|
||||
import { isDefined, isString, iter } from "@k8slens/utilities";
|
||||
import { once } from "lodash";
|
||||
import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event";
|
||||
import { ipcRenderer } from "electron";
|
||||
@@ -60,7 +60,7 @@ export class CatalogEntityRegistry {
|
||||
// If the entity was not found but there are rawEntities to be processed,
|
||||
// try to process them and return the entity.
|
||||
// This might happen if an extension registered a new Catalog category.
|
||||
if (this.activeEntityId && !entity && this.rawEntities.length > 0) {
|
||||
if (this.activeEntityId.get() && !entity && this.rawEntities.length > 0) {
|
||||
this.processRawEntities();
|
||||
|
||||
return this.getActiveEntityById();
|
||||
@@ -70,8 +70,8 @@ export class CatalogEntityRegistry {
|
||||
}
|
||||
|
||||
set activeEntity(raw: CatalogEntity | string | undefined) {
|
||||
if (raw) {
|
||||
const id = typeof raw === "string"
|
||||
if (isDefined(raw)) {
|
||||
const id = isString(raw)
|
||||
? raw
|
||||
: raw.getId();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { ImgHTMLAttributes, MouseEventHandler } from "react";
|
||||
import React from "react";
|
||||
import randomColor from "randomcolor";
|
||||
import type { StrictReactNode } from "@k8slens/utilities";
|
||||
import { cssNames } from "@k8slens/utilities";
|
||||
import { isTruthy, cssNames } from "@k8slens/utilities";
|
||||
import { computeDefaultShortName } from "../../../common/catalog/helpers";
|
||||
|
||||
export interface AvatarProps {
|
||||
@@ -44,11 +44,13 @@ export const Avatar = ({
|
||||
"data-testid": dataTestId,
|
||||
}: AvatarProps) => (
|
||||
<div
|
||||
className={cssNames(styles.Avatar, {
|
||||
[styles.circle]: variant == "circle",
|
||||
[styles.rounded]: variant == "rounded",
|
||||
[styles.disabled]: disabled,
|
||||
}, className)}
|
||||
className={cssNames(
|
||||
styles.Avatar,
|
||||
variant == "circle" && styles.circle,
|
||||
variant == "rounded" && styles.rounded,
|
||||
disabled && styles.disabled,
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
@@ -62,14 +64,18 @@ export const Avatar = ({
|
||||
onClick={onClick}
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
{src
|
||||
? (
|
||||
<img
|
||||
src={src}
|
||||
{...imgProps}
|
||||
alt={title}
|
||||
/>
|
||||
)
|
||||
: children || computeDefaultShortName(title)}
|
||||
{
|
||||
src
|
||||
? (
|
||||
<img
|
||||
src={src}
|
||||
{...imgProps}
|
||||
alt={title}
|
||||
/>
|
||||
)
|
||||
: isTruthy(children)
|
||||
? children
|
||||
: computeDefaultShortName(title)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@@ -63,15 +63,17 @@ export const Badge = withTooltip(observer(({
|
||||
return (
|
||||
<div
|
||||
{...elemProps}
|
||||
className={cssNames(styles.badge, className, {
|
||||
[styles.small]: small,
|
||||
[styles.flat]: flat,
|
||||
[styles.clickable]: Boolean(elemProps.onClick) || isExpandable,
|
||||
[styles.interactive]: isExpandable,
|
||||
[styles.isExpanded]: isExpanded,
|
||||
[styles.disabled]: disabled,
|
||||
[styles.scrollable]: scrollable,
|
||||
})}
|
||||
className={cssNames(
|
||||
styles.badge,
|
||||
className,
|
||||
small && styles.small,
|
||||
flat && styles.flat,
|
||||
(Boolean(elemProps.onClick) || isExpandable) && styles.clickable,
|
||||
isExpandable && styles.interactive,
|
||||
isExpanded && styles.isExpanded,
|
||||
disabled && styles.disabled,
|
||||
scrollable && styles.scrollable,
|
||||
)}
|
||||
onMouseUp={onMouseUp}
|
||||
ref={elem}
|
||||
>
|
||||
|
||||
@@ -11,9 +11,7 @@ import { observer } from "mobx-react";
|
||||
import { observable, action } from "mobx";
|
||||
import type { CatalogCategory, CatalogEntityAddMenu } from "../../api/catalog-entity";
|
||||
import { EventEmitter } from "events";
|
||||
import type { CatalogCategoryRegistry } from "../../../common/catalog";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
|
||||
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||
|
||||
@@ -24,7 +22,6 @@ export interface CatalogAddButtonProps {
|
||||
type CategoryId = string;
|
||||
|
||||
interface Dependencies {
|
||||
catalogCategoryRegistry: CatalogCategoryRegistry;
|
||||
navigate: Navigate;
|
||||
}
|
||||
|
||||
@@ -43,19 +40,9 @@ class NonInjectedCatalogAddButton extends React.Component<CatalogAddButtonProps
|
||||
}
|
||||
}
|
||||
|
||||
get categories() {
|
||||
return this.props.catalogCategoryRegistry.filteredItems;
|
||||
}
|
||||
|
||||
updateMenuItems = action(() => {
|
||||
this.menuItems.clear();
|
||||
|
||||
if (this.props.category) {
|
||||
this.updateCategoryItems(this.props.category);
|
||||
} else {
|
||||
// Show menu items from all categories
|
||||
this.categories.forEach(this.updateCategoryItems);
|
||||
}
|
||||
this.updateCategoryItems(this.props.category);
|
||||
});
|
||||
|
||||
updateCategoryItems = action((category: CatalogCategory) => {
|
||||
@@ -84,16 +71,13 @@ class NonInjectedCatalogAddButton extends React.Component<CatalogAddButtonProps
|
||||
|
||||
onButtonClick = () => {
|
||||
const defaultAction = this.items.find(item => item.defaultAction)?.onClick;
|
||||
const clickAction = defaultAction || (this.items.length === 1 ? this.items[0].onClick : null);
|
||||
const clickAction = defaultAction ?? this.items[0]?.onClick;
|
||||
|
||||
void clickAction?.();
|
||||
};
|
||||
|
||||
get items() {
|
||||
const { category } = this.props;
|
||||
|
||||
return category ? this.getCategoryFilteredItems(category) :
|
||||
this.categories.map(this.getCategoryFilteredItems).flat();
|
||||
return this.getCategoryFilteredItems(this.props.category);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -136,7 +120,6 @@ class NonInjectedCatalogAddButton extends React.Component<CatalogAddButtonProps
|
||||
export const CatalogAddButton = withInjectables<Dependencies, CatalogAddButtonProps>(NonInjectedCatalogAddButton, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||
navigate: di.inject(navigateInjectable),
|
||||
}),
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { isTruthy } from "@k8slens/utilities";
|
||||
import React from "react";
|
||||
import type { CatalogCategory } from "../../../common/catalog/catalog-entity";
|
||||
|
||||
@@ -18,7 +19,7 @@ export const CatalogCategoryLabel = ({ category }: CatalogCategoryLabelProps) =>
|
||||
return (
|
||||
<div className="flex">
|
||||
<div>{category.metadata.name}</div>
|
||||
{badge ? (<div className="flex items-center">{badge}</div>) : null}
|
||||
{isTruthy(badge) ? (<div className="flex items-center">{badge}</div>) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -43,10 +43,6 @@ class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.
|
||||
}
|
||||
|
||||
getMenuItems(entity: T): React.ReactChild[] {
|
||||
if (!entity) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const items = this.menuItems
|
||||
.map(this.props.normalizeMenuItem)
|
||||
.filter(hasDefiniteField("icon"))
|
||||
|
||||
+20
-16
@@ -15,22 +15,26 @@ const kubernetesClusterDetailsItemInjectable = getInjectable({
|
||||
kind: KubernetesCluster.kind,
|
||||
orderNumber: 40,
|
||||
components: {
|
||||
Details: ({ entity }) => (
|
||||
<>
|
||||
<DrawerTitle>Kubernetes Information</DrawerTitle>
|
||||
<div className="box grow EntityMetadata">
|
||||
<DrawerItem
|
||||
name="Distribution"
|
||||
data-testid={`kubernetes-distro-for-${entity.getId()}`}
|
||||
>
|
||||
{entity.metadata.distro || "unknown"}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Kubelet Version">
|
||||
{entity.metadata.kubeVersion || "unknown"}
|
||||
</DrawerItem>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
Details: ({ entity }) => {
|
||||
const kube = entity as KubernetesCluster;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle>Kubernetes Information</DrawerTitle>
|
||||
<div className="box grow EntityMetadata">
|
||||
<DrawerItem
|
||||
name="Distribution"
|
||||
data-testid={`kubernetes-distro-for-${entity.getId()}`}
|
||||
>
|
||||
{kube.metadata.distro || "unknown"}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Kubelet Version">
|
||||
{kube.metadata.kubeVersion || "unknown"}
|
||||
</DrawerItem>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
}),
|
||||
injectionToken: catalogEntityDetailItemInjectionToken,
|
||||
|
||||
@@ -137,7 +137,7 @@ const NonInjectedBarChart = observer(({
|
||||
typeof datasetIndex === "number"
|
||||
? {
|
||||
borderColor: "darkgray",
|
||||
backgroundColor: datasets[datasetIndex].borderColor as string,
|
||||
backgroundColor: datasets[datasetIndex]?.borderColor as string,
|
||||
}
|
||||
: {
|
||||
borderColor: "darkgray",
|
||||
@@ -181,7 +181,7 @@ const tooltipCallbackWith = (precision: number): ChartTooltipCallback["label"] =
|
||||
return "";
|
||||
}
|
||||
|
||||
const { label, data } = datasets[datasetIndex];
|
||||
const { label, data } = datasets[datasetIndex] ?? {};
|
||||
|
||||
if (!label || !data) {
|
||||
return "<unknown>";
|
||||
|
||||
@@ -36,7 +36,7 @@ const memoryLikeOptions: ChartOptions = {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { label = "", data } = datasets[datasetIndex];
|
||||
const { label = "", data } = datasets[datasetIndex] ?? {};
|
||||
|
||||
if (!data) {
|
||||
return label;
|
||||
@@ -78,7 +78,7 @@ export const metricTabOptions: Record<MetricsTab, ChartOptions> = {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { label = "", data } = datasets[datasetIndex];
|
||||
const { label = "", data } = datasets[datasetIndex] ?? {};
|
||||
|
||||
if (!data) {
|
||||
return label;
|
||||
@@ -106,7 +106,7 @@ export const metricTabOptions: Record<MetricsTab, ChartOptions> = {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { label = "", data } = datasets[datasetIndex];
|
||||
const { label = "", data } = datasets[datasetIndex] ?? {};
|
||||
|
||||
if (!data) {
|
||||
return label;
|
||||
@@ -134,7 +134,7 @@ export const metricTabOptions: Record<MetricsTab, ChartOptions> = {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { label = "", data } = datasets[datasetIndex];
|
||||
const { label = "", data } = datasets[datasetIndex] ?? {};
|
||||
|
||||
if (!data) {
|
||||
return label;
|
||||
|
||||
@@ -80,7 +80,7 @@ const NonInjectedPieChart = observer(({
|
||||
return false;
|
||||
}
|
||||
|
||||
const { data = [] } = datasets[datasetIndex];
|
||||
const { data = [] } = datasets[datasetIndex] ?? {};
|
||||
|
||||
if (datasets.length === 1) return true;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import "./checkbox.scss";
|
||||
import React from "react";
|
||||
import type { StrictReactNode } from "@k8slens/utilities";
|
||||
import { cssNames, noop } from "@k8slens/utilities";
|
||||
import { isTruthy, cssNames, noop } from "@k8slens/utilities";
|
||||
|
||||
export interface CheckboxProps {
|
||||
className?: string;
|
||||
@@ -35,7 +35,7 @@ export function Checkbox({ label, inline, className, value, children, onChange =
|
||||
onChange={event => onChange(event.target.checked, event)}
|
||||
/>
|
||||
<i className="box flex align-center"/>
|
||||
{label ? <span className="label">{label}</span> : null}
|
||||
{isTruthy(label) ? <span className="label">{label}</span> : null}
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TopBar } from "../layout/top-bar/top-bar";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
|
||||
import welcomeRouteInjectable from "../../../common/front-end-routing/routes/welcome/welcome-route.injectable";
|
||||
import { buildURL } from "@k8slens/utilities";
|
||||
import { buildURL, isDefined } from "@k8slens/utilities";
|
||||
import type { WatchForGeneralEntityNavigation } from "../../api/helpers/watch-for-general-entity-navigation.injectable";
|
||||
import watchForGeneralEntityNavigationInjectable from "../../api/helpers/watch-for-general-entity-navigation.injectable";
|
||||
import currentPathInjectable from "../../routes/current-path.injectable";
|
||||
@@ -39,7 +39,7 @@ class NonInjectedClusterManager extends React.Component<Dependencies> {
|
||||
renderMainComponent() {
|
||||
const Component = this.props.currentRouteComponent.get();
|
||||
|
||||
if (Component) {
|
||||
if (isDefined(Component)) {
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class NonInjectedClusterPrometheusSetting extends React.Component<ClusterPrometh
|
||||
}
|
||||
|
||||
@computed get canEditPrometheusPath(): boolean {
|
||||
if (!this.selectedOption || this.selectedOption === autoDetectPrometheus) {
|
||||
if (this.selectedOption === autoDetectPrometheus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class NonInjectedClusterPrometheusSetting extends React.Component<ClusterPrometh
|
||||
}
|
||||
|
||||
parsePrometheusPath = () => {
|
||||
if (!this.selectedOption || !this.path) {
|
||||
if (!this.path) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = this.path.split(/\/|:/, 3);
|
||||
|
||||
@@ -14,7 +14,7 @@ import navigateToEntitySettingsInjectable from "../../../common/front-end-routin
|
||||
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
|
||||
|
||||
export interface ClusterNoMetricsProps {
|
||||
className: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
|
||||
+1
-3
@@ -212,9 +212,7 @@ function getInternalCommands(dependencies: Dependencies): CommandRegistration[]
|
||||
id: "entity.viewSettings",
|
||||
title: ({ entity }) => `${entity.kind}/${entity.getName()}: View Settings`,
|
||||
action: ({ entity }) => dependencies.navigateToEntitySettings(entity.getId()),
|
||||
isActive: ({ entity }) => (
|
||||
entity && dependencies.hasCatalogEntitySettingItems(entity)
|
||||
),
|
||||
isActive: ({ entity }) => dependencies.hasCatalogEntitySettingItems(entity),
|
||||
},
|
||||
{
|
||||
id: "cluster.openTerminal",
|
||||
|
||||
@@ -54,7 +54,7 @@ class NonInjectedHorizontalPodAutoscalerDetails extends React.Component<KubeObje
|
||||
}
|
||||
|
||||
renderMetrics(hpa: HorizontalPodAutoscaler) {
|
||||
const renderName = (metric: HorizontalPodAutoscalerMetricSpec) => {
|
||||
const renderName = (metric: HorizontalPodAutoscalerMetricSpec | undefined) => {
|
||||
const metricName = getMetricName(metric);
|
||||
|
||||
switch (metric?.type) {
|
||||
@@ -95,7 +95,7 @@ class NonInjectedHorizontalPodAutoscalerDetails extends React.Component<KubeObje
|
||||
this.props.getMetrics(hpa)
|
||||
.map((metrics, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="name">{renderName(hpa.getMetrics()[index])}</TableCell>
|
||||
<TableCell className="name">{renderName(hpa.getMetrics()?.[index])}</TableCell>
|
||||
<TableCell className="metrics">{metrics}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
@@ -107,10 +107,6 @@ class NonInjectedHorizontalPodAutoscalerDetails extends React.Component<KubeObje
|
||||
render() {
|
||||
const { object: hpa, apiManager, getDetailsUrl, logger } = this.props;
|
||||
|
||||
if (!hpa) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(hpa instanceof HorizontalPodAutoscaler)) {
|
||||
logger.error("[HpaDetails]: passed object that is not an instanceof HorizontalPodAutoscaler", hpa);
|
||||
|
||||
|
||||
@@ -59,10 +59,6 @@ class NonInjectedLimitRangeDetails extends React.Component<KubeObjectDetailsProp
|
||||
render() {
|
||||
const { object: limitRange, logger } = this.props;
|
||||
|
||||
if (!limitRange) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(limitRange instanceof LimitRange)) {
|
||||
logger.error("[LimitRangeDetails]: passed object that is not an instanceof LimitRange", limitRange);
|
||||
|
||||
|
||||
@@ -44,9 +44,7 @@ class NonInjectedConfigMapDetails extends React.Component<KubeObjectDetailsProps
|
||||
autorun(() => {
|
||||
const configMap = this.props.object as ConfigMap;
|
||||
|
||||
if (configMap) {
|
||||
this.data.replace(configMap.data); // refresh
|
||||
}
|
||||
this.data.replace(configMap.data); // refresh
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -79,10 +77,6 @@ class NonInjectedConfigMapDetails extends React.Component<KubeObjectDetailsProps
|
||||
render() {
|
||||
const { object: configMap, logger } = this.props;
|
||||
|
||||
if (!configMap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(configMap instanceof ConfigMap)) {
|
||||
logger.error("[ConfigMapDetails]: passed object that is not an instanceof ConfigMap", configMap);
|
||||
|
||||
|
||||
+5
-7
@@ -144,13 +144,11 @@ export const WebhookConfig: React.FC<WebhookProps> = ({ webhook }) => {
|
||||
{" "}
|
||||
{rule.operations.join(", ")}
|
||||
</div>
|
||||
{rule.resources && (
|
||||
<div>
|
||||
Resources:
|
||||
{" "}
|
||||
{rule.resources.join(", ")}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
Resources:
|
||||
{" "}
|
||||
{rule.resources?.join(", ")}
|
||||
</div>
|
||||
{rule.scope && (
|
||||
<div>
|
||||
Scope:
|
||||
|
||||
-4
@@ -83,10 +83,6 @@ class NonInjectedResourceQuotaDetails extends React.Component<KubeObjectDetailsP
|
||||
render() {
|
||||
const { object: quota } = this.props;
|
||||
|
||||
if (!quota) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(quota instanceof ResourceQuota)) {
|
||||
this.props.logger.error("[ResourceQuotaDetails]: passed object that is not an instanceof ResourceQuota", quota);
|
||||
|
||||
|
||||
@@ -48,10 +48,8 @@ class NonInjectedSecretDetails extends React.Component<KubeObjectDetailsProps &
|
||||
autorun(() => {
|
||||
const secret = this.props.object as Secret;
|
||||
|
||||
if (secret) {
|
||||
this.data = secret.data;
|
||||
this.revealSecret.clear();
|
||||
}
|
||||
this.data = secret.data;
|
||||
this.revealSecret.clear();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -145,10 +143,6 @@ class NonInjectedSecretDetails extends React.Component<KubeObjectDetailsProps &
|
||||
render() {
|
||||
const { object: secret, logger } = this.props;
|
||||
|
||||
if (!secret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(secret instanceof Secret)) {
|
||||
logger.error("[SecretDetails]: passed object that is not an instanceof Secret", secret);
|
||||
|
||||
|
||||
+1
-8
@@ -161,10 +161,6 @@ class NonInjectedVpaDetails extends React.Component<KubeObjectDetailsProps & Dep
|
||||
render() {
|
||||
const { object: vpa, apiManager, getDetailsUrl, logger } = this.props;
|
||||
|
||||
if (!vpa) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(vpa instanceof VerticalPodAutoscaler)) {
|
||||
logger.error("[VpaDetails]: passed object that is not an instanceof VerticalPodAutoscaler", vpa);
|
||||
|
||||
@@ -186,10 +182,7 @@ class NonInjectedVpaDetails extends React.Component<KubeObjectDetailsProps & Dep
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerItem name="Recommender">
|
||||
{
|
||||
/* according to the spec there can be 0 or 1 recommenders, only */
|
||||
recommenders?.length ? recommenders[0].name : "default"
|
||||
}
|
||||
{recommenders?.[0]?.name ?? "default"}
|
||||
</DrawerItem>
|
||||
|
||||
{vpa.status && this.renderStatus(vpa, vpa.status)}
|
||||
|
||||
@@ -28,10 +28,6 @@ class NonInjectedCustomResourceDefinitionDetails extends React.Component<KubeObj
|
||||
render() {
|
||||
const { object: crd } = this.props;
|
||||
|
||||
if (!crd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(crd instanceof CustomResourceDefinition)) {
|
||||
this.props.logger.error("[CustomResourceDefinitionDetails]: passed object that is not an instanceof CustomResourceDefinition", crd);
|
||||
|
||||
|
||||
@@ -110,9 +110,9 @@ class NonInjectedCustomResourceDetails extends React.Component<CustomResourceDet
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props: { object, crd, logger }} = this;
|
||||
const { object, crd, logger } = this.props;
|
||||
|
||||
if (!object || !crd) {
|
||||
if (!crd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { CreateStorage } from "../../../utils/create-storage/create-storage
|
||||
import type { TabId } from "../dock/store";
|
||||
import autoBind from "auto-bind";
|
||||
import { toJS } from "../../../../common/utils";
|
||||
import { isTruthy } from "@k8slens/utilities";
|
||||
|
||||
export interface DockTabStoreOptions {
|
||||
autoInit?: boolean; // load data from storage when `storageKey` is provided and bind events, default: true
|
||||
@@ -59,7 +60,7 @@ export class DockTabStore<T> {
|
||||
|
||||
findTabIdFromData(inspector: (val: T) => unknown): TabId | undefined {
|
||||
for (const [tabId, data] of this.data) {
|
||||
if (inspector(data)) {
|
||||
if (isTruthy(inspector(data))) {
|
||||
return tabId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,10 +78,6 @@ class NonInjectedDockTab extends React.Component<DockTabProps & Dependencies> {
|
||||
render() {
|
||||
const { className, moreActions, dockStore, active, isMac, ...tabProps } = this.props;
|
||||
|
||||
if (!tabProps.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
void dockStore;
|
||||
void active;
|
||||
void isMac;
|
||||
@@ -94,9 +90,11 @@ class NonInjectedDockTab extends React.Component<DockTabProps & Dependencies> {
|
||||
<Tab
|
||||
{...tabProps}
|
||||
id={`tab-${id}`}
|
||||
className={cssNames(styles.DockTab, className, {
|
||||
[styles.pinned]: pinned,
|
||||
})}
|
||||
className={cssNames(
|
||||
styles.DockTab,
|
||||
className,
|
||||
pinned && styles.pinned,
|
||||
)}
|
||||
onContextMenu={() => this.menuVisible.set(true)}
|
||||
label={(
|
||||
<div className="flex align-center" onAuxClick={isMiddleClick(close)}>
|
||||
|
||||
@@ -93,11 +93,11 @@ class NonInjectedDock extends React.Component<DockProps & Dependencies> {
|
||||
const currentIndex = tabs.indexOf(selectedTab);
|
||||
const nextIndex = currentIndex + direction;
|
||||
|
||||
// check if moving to the next or previous tab is possible.
|
||||
if (nextIndex >= tabs.length || nextIndex < 0) return;
|
||||
|
||||
const nextElement = tabs[nextIndex];
|
||||
|
||||
// check if moving to the next or previous tab is possible.
|
||||
if (!nextElement) return;
|
||||
|
||||
this.onChangeTab(nextElement);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import React, { Component } from "react";
|
||||
import { computed, observable, reaction, makeObservable } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import type { StrictReactNode } from "@k8slens/utilities";
|
||||
import { cssNames } from "@k8slens/utilities";
|
||||
import { cssNames, isTruthy } from "@k8slens/utilities";
|
||||
import { Button } from "@k8slens/button";
|
||||
import { Icon } from "@k8slens/icon";
|
||||
import { Spinner } from "../spinner";
|
||||
@@ -90,7 +90,7 @@ class NonInjectedInfoPanel extends Component<InfoPanelProps & Dependencies> {
|
||||
try {
|
||||
const result = await this.props.submit?.();
|
||||
|
||||
if (showNotifications && result) {
|
||||
if (showNotifications && isTruthy(result)) {
|
||||
this.props.showSuccessNotification(result);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class NonInjectedInfoPanel extends Component<InfoPanelProps & Dependencies> {
|
||||
submitAndClose = async () => {
|
||||
const result = await this.submit();
|
||||
|
||||
if (result) {
|
||||
if (isTruthy(result)) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ export const LogSearch = observer((props: PodLogSearchProps) => {
|
||||
const logs = tabData.showTimestamps
|
||||
? model.logs.get()
|
||||
: model.logsWithoutTimestamps.get();
|
||||
const jumpDisabled = !searchQuery || !occurrences.length;
|
||||
const jumpDisabled = !searchQuery.get() || !occurrences.length;
|
||||
|
||||
const setSearch = (query: string) => {
|
||||
searchStore.onSearch(logs, query);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { when } from "mobx";
|
||||
import { loggerInjectionToken } from "@k8slens/logger";
|
||||
import { TerminalChannels } from "../../../../common/terminal/channels";
|
||||
import { waitUntilDefined, noop } from "@k8slens/utilities";
|
||||
import showSuccessNotificationInjectable from "../../notifications/show-success-notification.injectable";
|
||||
@@ -40,7 +39,6 @@ const sendCommandInjectable = getInjectable({
|
||||
const selectTab = di.inject(selectDockTabInjectable);
|
||||
const getTerminalApi = di.inject(getTerminalApiInjectable);
|
||||
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
|
||||
const logger = di.inject(loggerInjectionToken);
|
||||
|
||||
return async (command: string, options: SendCommandOptions = {}): Promise<void> => {
|
||||
let tabId: string | undefined = options.tabId;
|
||||
@@ -70,21 +68,14 @@ const sendCommandInjectable = getInjectable({
|
||||
await shellIsReady.catch(noop);
|
||||
clearTimeout(notifyVeryLong);
|
||||
|
||||
if (terminalApi) {
|
||||
if (options.enter) {
|
||||
command += "\r";
|
||||
}
|
||||
|
||||
terminalApi.sendMessage({
|
||||
type: TerminalChannels.STDIN,
|
||||
data: command,
|
||||
});
|
||||
} else {
|
||||
logger.warn(
|
||||
"The selected tab is does not have a connection. Cannot send command.",
|
||||
{ tabId, command },
|
||||
);
|
||||
if (options.enter) {
|
||||
command += "\r";
|
||||
}
|
||||
|
||||
terminalApi.sendMessage({
|
||||
type: TerminalChannels.STDIN,
|
||||
data: command,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,13 +44,11 @@ export class Terminal {
|
||||
protected readonly api: TerminalApi;
|
||||
|
||||
private get elem() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.xterm.element!;
|
||||
return this.xterm.element;
|
||||
}
|
||||
|
||||
private get viewport() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.elem.querySelector(".xterm-viewport")!;
|
||||
return this.elem?.querySelector(".xterm-viewport");
|
||||
}
|
||||
|
||||
attachTo(parentElem: HTMLElement) {
|
||||
@@ -99,8 +97,10 @@ export class Terminal {
|
||||
const onDataHandler = this.xterm.onData(this.onData);
|
||||
const clearOnce = once(this.onClear);
|
||||
|
||||
this.viewport.addEventListener("scroll", this.onScroll);
|
||||
this.elem.addEventListener("contextmenu", this.onContextMenu);
|
||||
const { viewport, elem } = this;
|
||||
|
||||
viewport?.addEventListener("scroll", this.onScroll);
|
||||
elem?.addEventListener("contextmenu", this.onContextMenu);
|
||||
this.api.once("ready", clearOnce);
|
||||
this.api.once("connected", clearOnce);
|
||||
this.api.on("data", this.onApiData);
|
||||
@@ -128,7 +128,8 @@ export class Terminal {
|
||||
() => this.fitAddon.dispose(),
|
||||
() => this.api.removeAllListeners(),
|
||||
() => window.removeEventListener("resize", this.onResize),
|
||||
() => this.elem.removeEventListener("contextmenu", this.onContextMenu),
|
||||
() => elem?.removeEventListener("contextmenu", this.onContextMenu),
|
||||
() => viewport?.removeEventListener("scroll", this.onScroll),
|
||||
this.xterm.onResize(({ cols, rows }) => {
|
||||
this.api.sendTerminalSize(cols, rows);
|
||||
}),
|
||||
@@ -161,7 +162,7 @@ export class Terminal {
|
||||
};
|
||||
|
||||
onScroll = () => {
|
||||
this.scrollPos = this.viewport.scrollTop;
|
||||
this.scrollPos = this.viewport?.scrollTop ?? 0;
|
||||
};
|
||||
|
||||
onClear = () => {
|
||||
@@ -176,7 +177,12 @@ export class Terminal {
|
||||
onActivate = () => {
|
||||
this.fit();
|
||||
setTimeout(() => this.focus(), 250); // delay used to prevent focus on active tab
|
||||
this.viewport.scrollTop = this.scrollPos; // restore last scroll position
|
||||
|
||||
const { viewport } = this;
|
||||
|
||||
if (viewport) {
|
||||
viewport.scrollTop = this.scrollPos; // restore last scroll position
|
||||
}
|
||||
};
|
||||
|
||||
onContextMenu = () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Badge } from "../badge";
|
||||
import { KubeObject } from "@k8slens/kube-object";
|
||||
|
||||
export interface DrawerItemLabelsProps extends DrawerItemProps {
|
||||
labels: string[] | Partial<Record<string, string>>;
|
||||
labels?: string[] | Partial<Record<string, string>>;
|
||||
}
|
||||
|
||||
export function DrawerItemLabels(props: DrawerItemLabelsProps) {
|
||||
|
||||
@@ -28,10 +28,12 @@ export interface DrawerTitleProps {
|
||||
export function DrawerTitle({ className, children, size = "title" }: DrawerTitleProps) {
|
||||
return (
|
||||
<div
|
||||
className={cssNames(styles.DrawerTitle, className, {
|
||||
[styles.title]: size === "title",
|
||||
[styles["sub-title"]]: size === "sub-title",
|
||||
})}
|
||||
className={cssNames(
|
||||
styles.DrawerTitle,
|
||||
className,
|
||||
size === "title" && styles.title,
|
||||
size === "sub-title" && styles["sub-title"],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -36,10 +36,6 @@ const NonInjectedEventDetails = observer(({
|
||||
className,
|
||||
logger,
|
||||
}: Dependencies & KubeObjectDetailsProps) => {
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(event instanceof KubeEvent)) {
|
||||
logger.error("[EventDetails]: passed object that is not an instanceof KubeEvent", event);
|
||||
|
||||
|
||||
@@ -43,10 +43,6 @@ class NonInjectedKubeEventDetails extends React.Component<KubeEventDetailsProps
|
||||
render() {
|
||||
const { object, eventStore } = this.props;
|
||||
|
||||
if (!object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(object instanceof KubeObject)) {
|
||||
this.props.logger.error("[KubeEventDetails]: passed object that is not an instanceof KubeObject", object);
|
||||
|
||||
@@ -64,7 +60,7 @@ class NonInjectedKubeEventDetails extends React.Component<KubeEventDetailsProps
|
||||
<div className={styles.KubeEventDetails}>
|
||||
{events.map(event => (
|
||||
<div className={styles.event} key={event.getId()}>
|
||||
<div className={cssNames(styles.title, { [styles.warning]: event.isWarning() })}>
|
||||
<div className={cssNames(styles.title, event.isWarning() && styles.warning)}>
|
||||
{event.message}
|
||||
</div>
|
||||
<DrawerItem name="Source">
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { isObject } from "@k8slens/utilities";
|
||||
import { isObject, isTruthy } from "@k8slens/utilities";
|
||||
import React from "react";
|
||||
import { SemVer } from "semver";
|
||||
import URLParse from "url-parse";
|
||||
@@ -76,8 +76,8 @@ const attemptInstallByInfoInjectable = getInjectable({
|
||||
return disposer();
|
||||
}
|
||||
|
||||
if (result.value.error || !isObject(result.value.versions)) {
|
||||
const message = result.value.error ? `: ${String(result.value.error)}` : "";
|
||||
if (isTruthy(result.value.error) || !isObject(result.value.versions)) {
|
||||
const message = isTruthy(result.value.error) ? `: ${String(result.value.error)}` : "";
|
||||
|
||||
showErrorNotification(`Failed to get registry information for extension${message}`);
|
||||
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { hasTypedProperty, isDefined } from "@k8slens/utilities";
|
||||
import { hasTypedProperty, isDefined, isObject } from "@k8slens/utilities";
|
||||
|
||||
export function getMessageFromError(error: unknown): string {
|
||||
if (!error || typeof error !== "object") {
|
||||
if (!isObject(error)) {
|
||||
return "an error has occurred";
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ const NonInjectedInstalledExtensions = observer(({
|
||||
enableExtension,
|
||||
disableExtension,
|
||||
}: Dependencies) => {
|
||||
if (!extensionDiscovery.isLoaded) {
|
||||
if (!extensionDiscovery.isLoaded.get()) {
|
||||
return <div><Spinner center /></div>;
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ const NonInjectedInstalledExtensions = observer(({
|
||||
accessor: "extension",
|
||||
width: 200,
|
||||
sortType: (rowA, rowB) => { // Custom sorting for extension name
|
||||
const nameA = extensions[rowA.index].manifest.name;
|
||||
const nameB = extensions[rowB.index].manifest.name;
|
||||
const nameA = extensions[rowA.index]?.manifest.name ?? "";
|
||||
const nameB = extensions[rowB.index]?.manifest.name ?? "";
|
||||
|
||||
if (nameA > nameB) return -1;
|
||||
if (nameB > nameA) return 1;
|
||||
@@ -126,7 +126,12 @@ const NonInjectedInstalledExtensions = observer(({
|
||||
),
|
||||
version,
|
||||
status: (
|
||||
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
|
||||
<div
|
||||
className={cssNames(
|
||||
isEnabled && styles.enabled,
|
||||
!isCompatible && styles.invalid,
|
||||
)}
|
||||
>
|
||||
{getStatus(extension)}
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -25,7 +25,6 @@ import versionsOfSelectedHelmChartInjectable from "./details/versions-of-selecte
|
||||
import type { HelmChartDetailsVersionSelection } from "./details/versions/helm-chart-details-version-selection.injectable";
|
||||
import helmChartDetailsVersionSelectionInjectable from "./details/versions/helm-chart-details-version-selection.injectable";
|
||||
import assert from "assert";
|
||||
import autoBindReact from "auto-bind/react";
|
||||
|
||||
export interface HelmChartDetailsProps {
|
||||
hideDetails(): void;
|
||||
@@ -47,15 +46,6 @@ interface Dependencies {
|
||||
|
||||
@observer
|
||||
class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Dependencies> {
|
||||
constructor(props: HelmChartDetailsProps & Dependencies) {
|
||||
super(props);
|
||||
autoBindReact(this);
|
||||
}
|
||||
|
||||
get chart() {
|
||||
return this.props.chart;
|
||||
}
|
||||
|
||||
install = () => {
|
||||
const chart = this.props.versionSelection.value.get();
|
||||
|
||||
@@ -144,13 +134,13 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
||||
const readmeIsLoading = this.props.readme.pending.get();
|
||||
const versionsAreLoading = this.props.versions.pending.get();
|
||||
|
||||
if (!this.chart || versionsAreLoading) {
|
||||
if (versionsAreLoading) {
|
||||
return <Spinner center data-testid="spinner-for-chart-details" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="box grow">
|
||||
{this.renderIntroduction(this.chart)}
|
||||
{this.renderIntroduction(this.props.chart)}
|
||||
|
||||
{readmeIsLoading ? (
|
||||
<Spinner center data-testid="spinner-for-chart-readme" />
|
||||
@@ -162,12 +152,13 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
className="HelmChartDetails"
|
||||
usePortal={true}
|
||||
open={!!this.chart}
|
||||
title={this.chart ? `Chart: ${this.chart.getFullName()}` : ""}
|
||||
usePortal
|
||||
open
|
||||
title={`Chart: ${this.props.chart.getFullName()}`}
|
||||
onClose={this.props.hideDetails}
|
||||
>
|
||||
{this.renderContent()}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const HelmChartIcon = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssNames(styles.chartIcon, className, { [styles.imageNotLoaded]: !isImageLoaded })}
|
||||
className={cssNames(styles.chartIcon, className, !isImageLoaded && styles.imageNotLoaded)}
|
||||
data-testid="image-container"
|
||||
style={{
|
||||
backgroundImage: isImageLoaded ? backgroundImage : `url(${placeholderImageUrl})`,
|
||||
|
||||
@@ -46,12 +46,9 @@ class NonInjectedHelmReleaseMenu extends React.Component<HelmReleaseMenuProps &
|
||||
renderContent() {
|
||||
const { release, toolbar } = this.props;
|
||||
|
||||
if (!release) return null;
|
||||
const hasRollback = release && release.getRevision() > 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasRollback && (
|
||||
{(release.getRevision() > 1) && (
|
||||
<MenuItem onClick={this.rollback}>
|
||||
<Icon
|
||||
material="history"
|
||||
|
||||
@@ -69,10 +69,10 @@ class NonInjectedHotbarEntityIcon extends React.Component<HotbarEntityIconProps
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssNames(styles.led, {
|
||||
// TODO: make it more generic
|
||||
[styles.online]: this.props.entity.status.phase === LensKubernetesClusterStatus.CONNECTED,
|
||||
})}
|
||||
className={cssNames(
|
||||
styles.led,
|
||||
(this.props.entity.status.phase === LensKubernetesClusterStatus.CONNECTED) && styles.online,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -107,7 +107,6 @@ class NonInjectedHotbarEntityIcon extends React.Component<HotbarEntityIconProps
|
||||
className={className}
|
||||
active={this.isActive(entity)}
|
||||
onMenuOpen={() => this.onMenuOpen()}
|
||||
disabled={!entity}
|
||||
menuItems={this.menuItems}
|
||||
tooltip={(
|
||||
entity.metadata.source
|
||||
|
||||
@@ -50,7 +50,7 @@ const NonInjectedHotbarIcon = observer(({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cssNames(styles.HotbarIcon, className, { [styles.contextMenuAvailable]: menuItems.length > 0 })}>
|
||||
<div className={cssNames(styles.HotbarIcon, className, (menuItems.length > 0 && styles.contextMenuAvailable))}>
|
||||
{tooltip && (
|
||||
<Tooltip targetId={id}>
|
||||
{tooltip}
|
||||
@@ -61,7 +61,11 @@ const NonInjectedHotbarIcon = observer(({
|
||||
id={id}
|
||||
title={title}
|
||||
colorHash={source ? `${title}-${source}` : title}
|
||||
className={cssNames(styles.avatar, { [styles.active]: active, [styles.hasImage]: !!src })}
|
||||
className={cssNames(
|
||||
styles.avatar,
|
||||
active && styles.active,
|
||||
!!src && styles.hasImag,
|
||||
)}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
src={src}
|
||||
|
||||
@@ -8,7 +8,7 @@ import "./input.scss";
|
||||
import type { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
|
||||
import React from "react";
|
||||
import type { StrictReactNode, SingleOrMany } from "@k8slens/utilities";
|
||||
import { isReactNode, isString, isFunction, debouncePromise, isPromiseSettledFulfilled, cssNames } from "@k8slens/utilities";
|
||||
import { isReactNode, isFalsy, isTruthy, isString, isFunction, debouncePromise, isPromiseSettledFulfilled, cssNames } from "@k8slens/utilities";
|
||||
import { Icon } from "@k8slens/icon";
|
||||
import type { TooltipProps } from "@k8slens/tooltip";
|
||||
import { Tooltip } from "@k8slens/tooltip";
|
||||
@@ -195,7 +195,13 @@ export class Input extends React.Component<InputProps, State> {
|
||||
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
return this.getValidatorError(value, validator) || (error instanceof Error ? error.message : String(error));
|
||||
const validationError = this.getValidatorError(value, validator);
|
||||
|
||||
return isTruthy(validationError)
|
||||
? validationError
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: String(error);
|
||||
}
|
||||
})());
|
||||
}
|
||||
@@ -327,7 +333,7 @@ export class Input extends React.Component<InputProps, State> {
|
||||
get showMaxLenIndicator() {
|
||||
const { maxLength, multiLine } = this.props;
|
||||
|
||||
return maxLength && multiLine;
|
||||
return Boolean(maxLength && multiLine);
|
||||
}
|
||||
|
||||
get isUncontrolled() {
|
||||
@@ -440,12 +446,12 @@ export class Input extends React.Component<InputProps, State> {
|
||||
{errors.map((error, i) => <p key={i}>{error}</p>)}
|
||||
</div>
|
||||
);
|
||||
const componentId = id || showErrorsAsTooltip
|
||||
const componentId = id || isTruthy(showErrorsAsTooltip)
|
||||
? `input_tooltip_id_${uuid.v4()}`
|
||||
: undefined;
|
||||
let tooltipError: StrictReactNode;
|
||||
|
||||
if (showErrorsAsTooltip && showErrors) {
|
||||
if (isTruthy(showErrorsAsTooltip) && showErrors) {
|
||||
const tooltipProps = typeof showErrorsAsTooltip === "object" ? showErrorsAsTooltip : {};
|
||||
|
||||
tooltipProps.className = cssNames("InputTooltipError", tooltipProps.className);
|
||||
@@ -473,7 +479,7 @@ export class Input extends React.Component<InputProps, State> {
|
||||
{contentRight}
|
||||
</label>
|
||||
<div className="input-info flex gaps">
|
||||
{!showErrorsAsTooltip && showErrors && errorsInfo}
|
||||
{(isFalsy(showErrorsAsTooltip) && showErrors) && errorsInfo}
|
||||
{this.showMaxLenIndicator && (
|
||||
<div className="maxLengthIndicator box right">
|
||||
{this.getValue().length}
|
||||
|
||||
@@ -59,6 +59,10 @@ export function isAsyncValidator(validator: InputValidator): validator is AsyncI
|
||||
return typeof validator.debounce === "number";
|
||||
}
|
||||
|
||||
export function isSyncValidator(validator: InputValidator): validator is SyncInputValidator {
|
||||
return typeof validator.debounce !== "number";
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to create an {@link AsyncInputValidator}
|
||||
*/
|
||||
@@ -106,7 +110,11 @@ export function unionInputValidatorsAsync(
|
||||
debounce: longestDebounce,
|
||||
validate: async (value, props) => {
|
||||
for (const validator of validators) {
|
||||
if (isAsyncValidator(validator)) {
|
||||
if (isSyncValidator(validator)) {
|
||||
if (validator.validate(value, props)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await validator.validate(value, props);
|
||||
|
||||
@@ -114,10 +122,6 @@ export function unionInputValidatorsAsync(
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
} else {
|
||||
if (validator.validate(value, props)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import "./search-input.scss";
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { addWindowEventListener, cssNames, disposer } from "@k8slens/utilities";
|
||||
import { addWindowEventListener, cssNames, disposer, isTruthy } from "@k8slens/utilities";
|
||||
import { Icon } from "@k8slens/icon";
|
||||
import type { InputProps } from "./input";
|
||||
import { Input } from "./input";
|
||||
@@ -86,7 +86,7 @@ class NonInjectedSearchInput extends React.Component<SearchInputProps & Dependen
|
||||
void bindGlobalFocusHotkey;
|
||||
void isMac;
|
||||
|
||||
if (showClearIcon && value) {
|
||||
if (showClearIcon && isTruthy(value)) {
|
||||
rightIcon = (
|
||||
<Icon
|
||||
small
|
||||
|
||||
@@ -210,7 +210,7 @@ class NonInjectedItemListLayoutContent<
|
||||
const onConfirm = isFunction(removeItems)
|
||||
? () => removeItems.apply(store, [selectedItems])
|
||||
: isFunction(removeSelectedItems)
|
||||
? removeSelectedItems.bind(store)
|
||||
? () => removeSelectedItems.apply(store)
|
||||
: noop;
|
||||
|
||||
openConfirmDialog({
|
||||
@@ -328,7 +328,7 @@ class NonInjectedItemListLayoutContent<
|
||||
{() => (
|
||||
<AddRemoveButtons
|
||||
onRemove={
|
||||
(store.removeItems || store.removeSelectedItems) && selectedItems.length > 0
|
||||
(Boolean(store.removeItems) || Boolean(store.removeSelectedItems)) && selectedItems.length > 0
|
||||
? () => this.removeItemsDialog(selectedItems)
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import "./item-list-layout.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IClassName, StrictReactNode } from "@k8slens/utilities";
|
||||
import { isFunction, cssNames, isDefined } from "@k8slens/utilities";
|
||||
import { isTruthy, isFunction, cssNames, isDefined } from "@k8slens/utilities";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import type { Filter } from "./page-filters/store";
|
||||
import type { HeaderCustomizer, HeaderPlaceholders, ItemListStore, SearchFilter } from "./list-layout";
|
||||
@@ -91,7 +91,7 @@ export class ItemListLayoutHeader<I extends ItemObject, PreLoadStores extends bo
|
||||
<div className={cssNames("header flex gaps align-center", headerClassName)}>
|
||||
{title}
|
||||
{
|
||||
info && (
|
||||
isTruthy(info) && (
|
||||
<div className="info-panel box grow">
|
||||
{info}
|
||||
</div>
|
||||
|
||||
@@ -346,7 +346,7 @@ export const ItemListLayout = withInjectables<Dependencies, ItemListLayoutProps<
|
||||
}) as <I extends ItemObject, PreLoadStores extends boolean = true>(props: ItemListLayoutProps<I, PreLoadStores>) => React.ReactElement;
|
||||
|
||||
function applyFilters<I extends ItemObject>(filters: ItemsFilter<I>[], items: I[]): I[] {
|
||||
if (!filters || !filters.length) {
|
||||
if (!filters.length) {
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -76,7 +76,7 @@ const matchesApiFor = (api: SubscribableStore["api"]) => (column: GeneralKubeObj
|
||||
|
||||
const getLoadErrorMessage = (error: unknown): string => {
|
||||
if (error instanceof Error) {
|
||||
if (error.cause) {
|
||||
if (Boolean(error.cause)) {
|
||||
return `${error.message}: ${getLoadErrorMessage(error.cause)}`;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,19 @@ class NonInjectedKubeObjectListLayout<
|
||||
sortingCallbacks = {},
|
||||
...layoutProps
|
||||
} = this.props;
|
||||
const resourceName = this.props.resourceName || ResourceNames[ResourceKindMap[store.api.kind]] || store.api.kind;
|
||||
const resourceName = (() => {
|
||||
if (this.props.resourceName) {
|
||||
return this.props.resourceName;
|
||||
}
|
||||
|
||||
const kubeResource = ResourceKindMap[store.api.kind];
|
||||
|
||||
if (kubeResource) {
|
||||
return ResourceNames[kubeResource];
|
||||
}
|
||||
|
||||
return store.api.kind;
|
||||
})();
|
||||
const targetColumns = [
|
||||
...(columns ?? []),
|
||||
...generalColumns.filter(matchesApiFor(store.api)),
|
||||
|
||||
@@ -51,7 +51,7 @@ class NonInjectedKubeObjectMenu<Kube extends KubeObject> extends React.Component
|
||||
private menuItems = observable.array<KubeObjectContextMenuItem>();
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<KubeObjectMenuProps<Kube> & Dependencies>): void {
|
||||
if (prevProps.object !== this.props.object && this.props.object) {
|
||||
if (prevProps.object !== this.props.object) {
|
||||
this.emitOnContextMenuOpen(this.props.object);
|
||||
}
|
||||
}
|
||||
@@ -199,11 +199,11 @@ class NonInjectedKubeObjectMenu<Kube extends KubeObject> extends React.Component
|
||||
id={`menu-actions-for-kube-object-menu-for-${object?.getId()}`}
|
||||
data-testid={`menu-actions-for-kube-object-menu-for-${object?.getId()}`}
|
||||
className={cssNames("KubeObjectMenu", className)}
|
||||
onOpen={object ? () => this.emitOnContextMenuOpen(object) : undefined}
|
||||
onOpen={() => this.emitOnContextMenuOpen(object)}
|
||||
{...menuProps}
|
||||
>
|
||||
{this.renderMenuItems()}
|
||||
{object && this.renderContextMenuItems(object)}
|
||||
{this.renderContextMenuItems(object)}
|
||||
</MenuActions>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,10 +48,6 @@ const NonInjectedKubeObjectMeta = observer((props : Dependencies & KubeObjectMet
|
||||
namespaceApi,
|
||||
} = props;
|
||||
|
||||
if (!object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(object instanceof KubeObject)) {
|
||||
logger.error("[KubeObjectMeta]: passed object that is not an instanceof KubeObject", object);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import React from "react";
|
||||
import type { IObservableValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import type { StrictReactNode } from "@k8slens/utilities";
|
||||
import { cssNames } from "@k8slens/utilities";
|
||||
import { cssNames, isTruthy } from "@k8slens/utilities";
|
||||
import { Button } from "@k8slens/button";
|
||||
import type { DialogProps } from "../dialog";
|
||||
import { Dialog } from "../dialog";
|
||||
@@ -54,7 +54,7 @@ class NonInjectedKubeConfigDialog extends React.Component<KubeConfigDialogProps
|
||||
};
|
||||
|
||||
renderContents = (data: KubeconfigDialogData) => (
|
||||
<Wizard header={<h5>{ data.title || "Kubeconfig File" }</h5>}>
|
||||
<Wizard header={<h5>{isTruthy(data.title) ? data.title : "Kubeconfig File" }</h5>}>
|
||||
<WizardStep
|
||||
customButtons={ (
|
||||
<div className="actions flex gaps">
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ const extensionSidebarItemRegistratorInjectable = getInjectable({
|
||||
parentId,
|
||||
isVisible: computed(() => extensionShouldBeEnabledForClusterFrame.value.get() && (visible?.get() ?? true)),
|
||||
title,
|
||||
getIcon: () => (components.Icon && <components.Icon />),
|
||||
getIcon: () => <components.Icon />,
|
||||
onClick: () => {
|
||||
const route = targetRoute.get();
|
||||
|
||||
|
||||
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user