diff --git a/extensions/pod-menu/src/logs-menu.tsx b/extensions/pod-menu/src/logs-menu.tsx index 8932318dc6..e99e454fdf 100644 --- a/extensions/pod-menu/src/logs-menu.tsx +++ b/extensions/pod-menu/src/logs-menu.tsx @@ -15,7 +15,6 @@ export class PodLogsMenu extends React.Component { selectedContainer: container, showTimestamps: false, previous: false, - tailLines: 1000 }); } diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts index 492f02d1e6..eeef7a01f1 100644 --- a/integration/__tests__/app.tests.ts +++ b/integration/__tests__/app.tests.ts @@ -166,8 +166,8 @@ describe("Lens integration tests", () => { pages: [{ name: "Cluster", href: "cluster", - expectedSelector: "div.ClusterNoMetrics p", - expectedText: "Metrics are not available due" + expectedSelector: "div.Cluster div.label", + expectedText: "Master" }] }, { diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index 3bbec7841f..c2af9ea7ba 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -37,12 +37,12 @@ export class HelmRepoManager extends Singleton { async loadAvailableRepos(): Promise { const res = await customRequestPromise({ - uri: "https://hub.helm.sh/assets/js/repos.json", + uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json", json: true, resolveWithFullResponse: true, timeout: 10000, }); - return orderBy(res.body.data, repo => repo.name); + return orderBy(res.body, repo => repo.name); } async init() { diff --git a/src/renderer/api/endpoints/helm-releases.api.ts b/src/renderer/api/endpoints/helm-releases.api.ts index a3c1a62045..9051936ac8 100644 --- a/src/renderer/api/endpoints/helm-releases.api.ts +++ b/src/renderer/api/endpoints/helm-releases.api.ts @@ -141,7 +141,7 @@ export class HelmRelease implements ItemObject { chart: string status: string updated: string - revision: number + revision: string getId() { return this.namespace + this.name; @@ -165,7 +165,7 @@ export class HelmRelease implements ItemObject { } getRevision() { - return this.revision; + return parseInt(this.revision, 10); } getStatus() { diff --git a/src/renderer/api/endpoints/ingress.api.ts b/src/renderer/api/endpoints/ingress.api.ts index 9e6840c1f7..1f3e1659f0 100644 --- a/src/renderer/api/endpoints/ingress.api.ts +++ b/src/renderer/api/endpoints/ingress.api.ts @@ -109,6 +109,14 @@ export class Ingress extends KubeObject { } return ports.join(", ") } + + getLoadBalancers() { + const { status: { loadBalancer = { ingress: [] } } } = this; + + return (loadBalancer.ingress ?? []).map(address => ( + address.hostname || address.ip + )) + } } export const ingressApi = new IngressApi({ diff --git a/src/renderer/api/endpoints/pods.api.ts b/src/renderer/api/endpoints/pods.api.ts index ab58660eab..92b7059272 100644 --- a/src/renderer/api/endpoints/pods.api.ts +++ b/src/renderer/api/endpoints/pods.api.ts @@ -152,7 +152,16 @@ export interface IPodContainerStatus { reason: string; }; }; - lastState: {}; + lastState: { + [index: string]: object; + terminated?: { + startedAt: string; + finishedAt: string; + exitCode: number; + reason: string; + containerID: string; + }; + }; ready: boolean; restartCount: number; image: string; diff --git a/src/renderer/components/+network-ingresses/ingresses.tsx b/src/renderer/components/+network-ingresses/ingresses.tsx index 47ac9b3b47..7167e0ba7f 100644 --- a/src/renderer/components/+network-ingresses/ingresses.tsx +++ b/src/renderer/components/+network-ingresses/ingresses.tsx @@ -39,12 +39,14 @@ export class Ingresses extends React.Component { renderTableHeader={[ { title: Name, className: "name", sortBy: sortBy.name }, { title: Namespace, className: "namespace", sortBy: sortBy.namespace }, + { title: LoadBalancers, className: "loadbalancers" }, { title: Rules, className: "rules" }, { title: Age, className: "age", sortBy: sortBy.age }, ]} renderTableContents={(ingress: Ingress) => [ ingress.getName(), ingress.getNs(), + ingress.getLoadBalancers().map(lb =>

{lb}

), ingress.getRoutes().map(route =>

{route}

), ingress.getAge(), ]} diff --git a/src/renderer/components/+workloads-pods/pod-container-env.tsx b/src/renderer/components/+workloads-pods/pod-container-env.tsx index fb779a9eaa..2071c959d1 100644 --- a/src/renderer/components/+workloads-pods/pod-container-env.tsx +++ b/src/renderer/components/+workloads-pods/pod-container-env.tsx @@ -1,7 +1,6 @@ import "./pod-container-env.scss"; import React, { useEffect, useState } from "react"; -import flatten from "lodash/flatten"; import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { IPodContainer, Secret } from "../../api/endpoints"; @@ -11,6 +10,7 @@ import { secretsStore } from "../+config-secrets/secrets.store"; import { configMapsStore } from "../+config-maps/config-maps.store"; import { Icon } from "../icon"; import { base64, cssNames } from "../../utils"; +import _ from "lodash"; interface Props { container: IPodContainer; @@ -40,7 +40,9 @@ export const ContainerEnvironment = observer((props: Props) => { ) const renderEnv = () => { - return env.map(variable => { + const orderedEnv = _.sortBy(env, 'name'); + + return orderedEnv.map(variable => { const { name, value, valueFrom } = variable let secretValue = null @@ -89,7 +91,7 @@ export const ContainerEnvironment = observer((props: Props) => { )) }) - return flatten(envVars) + return _.flatten(envVars) } return ( diff --git a/src/renderer/components/+workloads-pods/pod-details-container.tsx b/src/renderer/components/+workloads-pods/pod-details-container.tsx index 79180b1130..df00721afd 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-container.tsx @@ -2,7 +2,7 @@ import "./pod-details-container.scss" import React from "react"; import { t, Trans } from "@lingui/macro"; -import { IPodContainer, Pod } from "../../api/endpoints"; +import { IPodContainer, IPodContainerStatus, Pod } from "../../api/endpoints"; import { DrawerItem } from "../drawer"; import { cssNames } from "../../utils"; import { StatusBrick } from "../status-brick"; @@ -21,12 +21,37 @@ interface Props { } export class PodDetailsContainer extends React.Component { + + renderStatus(state: string, status: IPodContainerStatus) { + const ready = status ? status.ready : "" + return ( + + {state}{ready ? `, ${_i18n._(t`ready`)}` : ""} + {state === 'terminated' ? ` - ${status.state.terminated.reason} (${_i18n._(t`exit code`)}: ${status.state.terminated.exitCode})` : ''} + + ); + } + + renderLastState(lastState: string, status: IPodContainerStatus) { + if (lastState === 'terminated') { + return ( + + {lastState}
+ {_i18n._(t`Reason`)}: {status.lastState.terminated.reason} - {_i18n._(t`exit code`)}: {status.lastState.terminated.exitCode}
+ {_i18n._(t`Started at`)}: {status.lastState.terminated.startedAt}
+ {_i18n._(t`Finished at`)}: {status.lastState.terminated.finishedAt}
+
+ ) + } + } + render() { const { pod, container, metrics } = this.props if (!pod || !container) return null const { name, image, imagePullPolicy, ports, volumeMounts, command, args } = container const status = pod.getContainerStatuses().find(status => status.name === container.name) const state = status ? Object.keys(status.state)[0] : "" + const lastState = status ? Object.keys(status.lastState)[0] : "" const ready = status ? status.ready : "" const liveness = pod.getLivenessProbe(container) const readiness = pod.getReadinessProbe(container) @@ -48,10 +73,12 @@ export class PodDetailsContainer extends React.Component { } {status && Status}> - - {state}{ready ? `, ${_i18n._(t`ready`)}` : ""} - {state === 'terminated' ? ` - ${status.state.terminated.reason} (${_i18n._(t`exit code`)}: ${status.state.terminated.exitCode})` : ''} - + {this.renderStatus(state, status)} + + } + {lastState && + Last Status}> + {this.renderLastState(lastState, status)} } Image}> diff --git a/src/renderer/components/dock/pod-log-controls.tsx b/src/renderer/components/dock/pod-log-controls.tsx new file mode 100644 index 0000000000..d3ba81e7ab --- /dev/null +++ b/src/renderer/components/dock/pod-log-controls.tsx @@ -0,0 +1,116 @@ +import React from "react"; +import { observer } from "mobx-react"; +import { IPodLogsData, podLogsStore } from "./pod-logs.store"; +import { t, Trans } from "@lingui/macro"; +import { Select, SelectOption } from "../select"; +import { Badge } from "../badge"; +import { Icon } from "../icon"; +import { _i18n } from "../../i18n"; +import { cssNames, downloadFile } from "../../utils"; +import { Pod } from "../../api/endpoints"; + +interface Props { + ready: boolean + tabId: string + tabData: IPodLogsData + logs: string[][] + save: (data: Partial) => void + reload: () => void +} + +export const PodLogControls = observer((props: Props) => { + if (!props.ready) return null; + const { tabData, tabId, save, reload, logs } = props; + const { selectedContainer, showTimestamps, previous } = tabData; + const since = podLogsStore.getTimestamps(podLogsStore.logs.get(tabId)[0]); + const pod = new Pod(tabData.pod); + const toggleTimestamps = () => { + save({ showTimestamps: !showTimestamps }); + } + + const togglePrevious = () => { + save({ previous: !previous }); + reload(); + } + + const downloadLogs = () => { + const fileName = selectedContainer ? selectedContainer.name : pod.getName(); + const [oldLogs, newLogs] = logs; + downloadFile(fileName + ".log", [...oldLogs, ...newLogs].join("\n"), "text/plain"); + } + + const onContainerChange = (option: SelectOption) => { + const { containers, initContainers } = tabData; + save({ + selectedContainer: containers + .concat(initContainers) + .find(container => container.name === option.value) + }) + reload(); + } + + const containerSelectOptions = () => { + const { containers, initContainers } = tabData; + return [ + { + label: _i18n._(t`Containers`), + options: containers.map(container => { + return { value: container.name } + }), + }, + { + label: _i18n._(t`Init Containers`), + options: initContainers.map(container => { + return { value: container.name } + }), + } + ]; + } + + const formatOptionLabel = (option: SelectOption) => { + const { value, label } = option; + return label || <> {value}; + } + + return ( +
+ Pod: + Namespace: + Container + - Lines -