Generic logs view with Pod selector (#1984)

* Adding LogTabStore

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding Pod selector in logs tab

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Refresh containers on pod change

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding <LogResourceSelector /> tests

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding LogTabStore tests

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Clearn getPodsByOwnerId method

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Extracting dummy pods into mock file

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Eliminating containers and initContainers from store

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Refreshing tab pods if pod amount is changed in store

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* A bit of cleaning up, fixing tests

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix lint newline errors

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Return getPodsByOwner() method

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Rename log tab when pod changes

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev authored and GitHub committed 2021-01-27 17:20:02 +03:00
1 parent 724c6c3265
commit a157eb03e6
25 files changed
+1359 -135

No files matched your search

+8 -1
View File
@@ -1,4 +1,5 @@
import { action, computed, observable } from "mobx";
import { action, computed, observable,reaction } from "mobx";
import { dockStore } from "../renderer/components/dock/dock.store";
import { autobind } from "../renderer/utils";
export class SearchStore {
@@ -6,6 +7,12 @@ export class SearchStore {
@observable occurrences: number[] = []; // Array with line numbers, eg [0, 0, 10, 21, 21, 40...]
@observable activeOverlayIndex = -1; // Index withing the occurences array. Showing where is activeOverlay currently located
constructor() {
reaction(() => dockStore.selectedTabId, () => {
searchStore.reset();
});
}
/**
* Sets default activeOverlayIndex
* @param text An array of any textual data (logs, for example)
+1 -1
View File
@@ -38,4 +38,4 @@ export * from "../../renderer/components/+events/kube-event-details";
// specific exports
export * from "../../renderer/components/status-brick";
export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store";
export { createPodLogsTab } from "../../renderer/components/dock/log.store";
export { logTabStore } from "../../renderer/components/dock/log-tab.store";
+1 -1
View File
@@ -21,7 +21,7 @@ export interface KubeJsonApiData extends JsonApiData {
resourceVersion: string;
continue?: string;
finalizers?: string[];
selfLink: string;
selfLink?: string;
labels?: {
[label: string]: string;
};
@@ -18,7 +18,7 @@ export class DaemonSetStore extends KubeObjectStore<DaemonSet> {
}
getChildPods(daemonSet: DaemonSet): Pod[] {
return podsStore.getPodsByOwner(daemonSet);
return podsStore.getPodsByOwnerId(daemonSet.getId());
}
getStatuses(daemonSets?: DaemonSet[]) {
@@ -10,7 +10,7 @@ export class JobStore extends KubeObjectStore<Job> {
api = jobApi;
getChildPods(job: Job): Pod[] {
return podsStore.getPodsByOwner(job);
return podsStore.getPodsByOwnerId(job.getId());
}
getJobsByOwner(cronJob: CronJob) {
@@ -3,8 +3,8 @@ import { action, observable } from "mobx";
import { KubeObjectStore } from "../../kube-object.store";
import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils";
import { IPodMetrics, Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { apiManager } from "../../api/api-manager";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
@autobind()
export class PodsStore extends KubeObjectStore<Pod> {
@@ -44,6 +44,12 @@ export class PodsStore extends KubeObjectStore<Pod> {
});
}
getPodsByOwnerId(workloadId: string): Pod[] {
return this.items.filter(pod => {
return pod.getOwnerRefs().find(owner => owner.uid === workloadId);
});
}
getPodsByNode(node: string) {
if (!this.isLoaded) return [];
@@ -18,7 +18,7 @@ export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
}
getChildPods(replicaSet: ReplicaSet) {
return podsStore.getPodsByOwner(replicaSet);
return podsStore.getPodsByOwnerId(replicaSet.getId());
}
getStatuses(replicaSets: ReplicaSet[]) {
@@ -17,7 +17,7 @@ export class StatefulSetStore extends KubeObjectStore<StatefulSet> {
}
getChildPods(statefulSet: StatefulSet) {
return podsStore.getPodsByOwner(statefulSet);
return podsStore.getPodsByOwnerId(statefulSet.getId());
}
getStatuses(statefulSets: StatefulSet[]) {
@@ -0,0 +1,103 @@
/**
* @jest-environment jsdom
*/
import React from "react";
import "@testing-library/jest-dom/extend-expect";
import { render } from "@testing-library/react";
import selectEvent from "react-select-event";
import { Pod } from "../../../api/endpoints";
import { LogResourceSelector } from "../log-resource-selector";
import { LogTabData } from "../log-tab.store";
import { dockerPod, deploymentPod1 } from "./pod.mock";
const getComponent = (tabData: LogTabData) => {
return (
<LogResourceSelector
tabId="tabId"
tabData={tabData}
save={jest.fn()}
reload={jest.fn()}
/>
);
};
const getOnePodTabData = (): LogTabData => {
const selectedPod = new Pod(dockerPod);
return {
pods: [] as Pod[],
selectedPod,
selectedContainer: selectedPod.getContainers()[0],
};
};
const getFewPodsTabData = (): LogTabData => {
const selectedPod = new Pod(deploymentPod1);
const anotherPod = new Pod(dockerPod);
return {
pods: [anotherPod],
selectedPod,
selectedContainer: selectedPod.getContainers()[0],
};
};
describe("<LogResourceSelector />", () => {
it("renders w/o errors", () => {
const tabData = getOnePodTabData();
const { container } = render(getComponent(tabData));
expect(container).toBeInstanceOf(HTMLElement);
});
it("renders proper namespace", () => {
const tabData = getOnePodTabData();
const { getByTestId } = render(getComponent(tabData));
const ns = getByTestId("namespace-badge");
expect(ns).toHaveTextContent("default");
});
it("renders proper selected items within dropdowns", () => {
const tabData = getOnePodTabData();
const { getByText } = render(getComponent(tabData));
expect(getByText("dockerExporter")).toBeInTheDocument();
expect(getByText("docker-exporter")).toBeInTheDocument();
});
it("renders sibling pods in dropdown", () => {
const tabData = getFewPodsTabData();
const { container, getByText } = render(getComponent(tabData));
const podSelector: HTMLElement = container.querySelector(".pod-selector");
selectEvent.openMenu(podSelector);
expect(getByText("dockerExporter")).toBeInTheDocument();
expect(getByText("deploymentPod1")).toBeInTheDocument();
});
it("renders sibling containers in dropdown", () => {
const tabData = getFewPodsTabData();
const { getByText, container } = render(getComponent(tabData));
const containerSelector: HTMLElement = container.querySelector(".container-selector");
selectEvent.openMenu(containerSelector);
expect(getByText("node-exporter-1")).toBeInTheDocument();
expect(getByText("init-node-exporter")).toBeInTheDocument();
expect(getByText("init-node-exporter-1")).toBeInTheDocument();
});
it("renders pod owner as dropdown title", () => {
const tabData = getFewPodsTabData();
const { getByText, container } = render(getComponent(tabData));
const podSelector: HTMLElement = container.querySelector(".pod-selector");
selectEvent.openMenu(podSelector);
expect(getByText("super-deployment")).toBeInTheDocument();
});
});
@@ -0,0 +1,113 @@
/**
* @jest-environment jsdom
*/
import { podsStore } from "../../+workloads-pods/pods.store";
import { Pod } from "../../../api/endpoints";
import { dockStore } from "../dock.store";
import { logTabStore } from "../log-tab.store";
import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock";
podsStore.items.push(new Pod(dockerPod));
podsStore.items.push(new Pod(deploymentPod1));
podsStore.items.push(new Pod(deploymentPod2));
describe("log tab store", () => {
afterEach(() => {
logTabStore.reset();
dockStore.reset();
});
it("creates log tab without sibling pods", () => {
const selectedPod = new Pod(dockerPod);
const selectedContainer = selectedPod.getAllContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("creates log tab with sibling pods", () => {
const selectedPod = new Pod(deploymentPod1);
const siblingPod = new Pod(deploymentPod2);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod, siblingPod],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("removes item from pods list if pod deleted from store", () => {
const selectedPod = new Pod(deploymentPod1);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
podsStore.items.pop();
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("adds item into pods list if new sibling pod added to store", () => {
const selectedPod = new Pod(deploymentPod1);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
podsStore.items.push(new Pod(deploymentPod3));
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod, deploymentPod3],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("closes tab if no pods left in store", () => {
const selectedPod = new Pod(deploymentPod1);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
podsStore.items.clear();
expect(logTabStore.getData(dockStore.selectedTabId)).toBeUndefined();
expect(dockStore.getTabById(dockStore.selectedTabId)).toBeUndefined();
});
});
@@ -0,0 +1,203 @@
export const dockerPod = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "dockerExporter",
name: "dockerExporter",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default"
},
spec: {
initContainers: [] as any,
containers: [
{
name: "docker-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
export const deploymentPod1 = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "deploymentPod1",
name: "deploymentPod1",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default",
ownerReferences: [{
apiVersion: "v1",
kind: "Deployment",
name: "super-deployment",
uid: "uuid",
controller: true,
blockOwnerDeletion: true,
}]
},
spec: {
initContainers: [
{
name: "init-node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "init-node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
export const deploymentPod2 = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "deploymentPod2",
name: "deploymentPod2",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default",
ownerReferences: [{
apiVersion: "v1",
kind: "Deployment",
name: "super-deployment",
uid: "uuid",
controller: true,
blockOwnerDeletion: true,
}]
},
spec: {
initContainers: [
{
name: "init-node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "init-node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
export const deploymentPod3 = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "deploymentPod3",
name: "deploymentPod3",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default",
ownerReferences: [{
apiVersion: "v1",
kind: "Deployment",
name: "super-deployment",
uid: "uuid",
controller: true,
blockOwnerDeletion: true,
}]
},
spec: {
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
+1 -1
View File
@@ -7,7 +7,7 @@ import { DockTab } from "./dock-tab";
import { IDockTab } from "./dock.store";
import { isEditResourceTab } from "./edit-resource.store";
import { isInstallChartTab } from "./install-chart.store";
import { isLogsTab } from "./log.store";
import { isLogsTab } from "./log-tab.store";
import { TerminalTab } from "./terminal-tab";
import { isTerminalTab } from "./terminal.store";
import { isUpgradeChartTab } from "./upgrade-chart.store";
@@ -208,6 +208,12 @@ export class DockStore {
this.closeTabs(tabs);
}
renameTab(tabId: TabId, title: string) {
const tab = this.getTabById(tabId);
tab.title = title;
}
@action
selectTab(tabId: TabId) {
this.selectedTabId = this.getTabById(tabId)?.id ?? null;
+1 -1
View File
@@ -17,7 +17,7 @@ import { isEditResourceTab } from "./edit-resource.store";
import { InstallChart } from "./install-chart";
import { isInstallChartTab } from "./install-chart.store";
import { Logs } from "./logs";
import { isLogsTab } from "./log.store";
import { isLogsTab } from "./log-tab.store";
import { TerminalWindow } from "./terminal-window";
import { createTerminalTab, isTerminalTab } from "./terminal.store";
import { UpgradeChart } from "./upgrade-chart";
@@ -5,22 +5,23 @@ import { observer } from "mobx-react";
import { Pod } from "../../api/endpoints";
import { cssNames, saveFileDialog } from "../../utils";
import { IPodLogsData, podLogsStore } from "./log.store";
import { logStore } from "./log.store";
import { Checkbox } from "../checkbox";
import { Icon } from "../icon";
import { LogTabData } from "./log-tab.store";
interface Props {
tabData: IPodLogsData
tabData: LogTabData
logs: string[]
save: (data: Partial<IPodLogsData>) => void
save: (data: Partial<LogTabData>) => void
reload: () => void
}
export const LogControls = observer((props: Props) => {
const { tabData, save, reload, logs } = props;
const { showTimestamps, previous } = tabData;
const since = logs.length ? podLogsStore.getTimestamps(logs[0]) : null;
const pod = new Pod(tabData.pod);
const since = logs.length ? logStore.getTimestamps(logs[0]) : null;
const pod = new Pod(tabData.selectedPod);
const toggleTimestamps = () => {
save({ showTimestamps: !showTimestamps });
@@ -33,7 +34,7 @@ export const LogControls = observer((props: Props) => {
const downloadLogs = () => {
const fileName = pod.getName();
const logsToDownload = showTimestamps ? logs : podLogsStore.logsWithoutTimestamps;
const logsToDownload = showTimestamps ? logs : logStore.logsWithoutTimestamps;
saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
};
+4 -3
View File
@@ -14,7 +14,8 @@ import { Button } from "../button";
import { Icon } from "../icon";
import { Spinner } from "../spinner";
import { VirtualList } from "../virtual-list";
import { podLogsStore } from "./log.store";
import { logStore } from "./log.store";
import { logTabStore } from "./log-tab.store";
interface Props {
logs: string[]
@@ -77,10 +78,10 @@ export class LogList extends React.Component<Props> {
*/
@computed
get logs() {
const showTimestamps = podLogsStore.getData(this.props.id).showTimestamps;
const showTimestamps = logTabStore.getData(this.props.id).showTimestamps;
if (!showTimestamps) {
return podLogsStore.logsWithoutTimestamps;
return logStore.logsWithoutTimestamps;
}
return this.props.logs;
@@ -1,27 +1,30 @@
import "./log-resource-selector.scss";
import React from "react";
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { IPodContainer, Pod } from "../../api/endpoints";
import { Pod } from "../../api/endpoints";
import { Badge } from "../badge";
import { Select, SelectOption } from "../select";
import { IPodLogsData } from "./log.store";
import { LogTabData, logTabStore } from "./log-tab.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { TabId } from "./dock.store";
interface Props {
tabData: IPodLogsData
save: (data: Partial<IPodLogsData>) => void
tabId: TabId
tabData: LogTabData
save: (data: Partial<LogTabData>) => void
reload: () => void
}
export const LogResourceSelector = observer((props: Props) => {
const { tabData, save, reload } = props;
const { selectedContainer, containers, initContainers } = tabData;
const pod = new Pod(tabData.pod);
const { tabData, save, reload, tabId } = props;
const { selectedPod, selectedContainer, pods } = tabData;
const pod = new Pod(selectedPod);
const containers = pod.getContainers();
const initContainers = pod.getInitContainers();
const onContainerChange = (option: SelectOption) => {
const { containers, initContainers } = tabData;
save({
selectedContainer: containers
.concat(initContainers)
@@ -30,11 +33,18 @@ export const LogResourceSelector = observer((props: Props) => {
reload();
};
const getSelectOptions = (containers: IPodContainer[]) => {
return containers.map(container => {
const onPodChange = (option: SelectOption) => {
const selectedPod = podsStore.getByName(option.value, pod.getNs());
save({ selectedPod });
logTabStore.renameTab(tabId);
};
const getSelectOptions = (items: string[]) => {
return items.map(item => {
return {
value: container.name,
label: container.name
value: item,
label: item
};
});
};
@@ -42,24 +52,43 @@ export const LogResourceSelector = observer((props: Props) => {
const containerSelectOptions = [
{
label: `Containers`,
options: getSelectOptions(containers)
options: getSelectOptions(containers.map(container => container.name))
},
{
label: `Init Containers`,
options: getSelectOptions(initContainers),
options: getSelectOptions(initContainers.map(container => container.name)),
}
];
const podSelectOptions = [
{
label: pod.getOwnerRefs()[0]?.name,
options: getSelectOptions(pods.map(pod => pod.metadata.name))
}
];
useEffect(() => {
reload();
}, [selectedPod]);
return (
<div className="LogResourceSelector flex gaps align-center">
<span>Namespace</span> <Badge label={pod.getNs()}/>
<span>Pod</span> <Badge label={pod.getName()}/>
<span>Namespace</span> <Badge data-testid="namespace-badge" label={pod.getNs()}/>
<span>Pod</span>
<Select
options={podSelectOptions}
value={{ label: pod.getName(), value: pod.getName() }}
onChange={onPodChange}
autoConvertOptions={false}
className="pod-selector"
/>
<span>Container</span>
<Select
options={containerSelectOptions}
value={{ label: selectedContainer.name, value: selectedContainer.name }}
onChange={onContainerChange}
autoConvertOptions={false}
className="container-selector"
/>
</div>
);
@@ -0,0 +1,123 @@
import uniqueId from "lodash/uniqueId";
import { reaction } from "mobx";
import { podsStore } from "../+workloads-pods/pods.store";
import { IPodContainer, Pod } from "../../api/endpoints";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
export interface LogTabData {
pods: Pod[];
selectedPod: Pod;
selectedContainer: IPodContainer
showTimestamps?: boolean
previous?: boolean
}
interface PodLogsTabData {
selectedPod: Pod
selectedContainer: IPodContainer
}
interface WorkloadLogsTabData {
workload: WorkloadKubeObject
}
export class LogTabStore extends DockTabStore<LogTabData> {
constructor() {
super({
storageName: "pod_logs"
});
reaction(() => podsStore.items.length, () => {
this.updateTabsData();
});
}
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): void {
const podOwner = selectedPod.getOwnerRefs()[0];
const pods = podsStore.getPodsByOwnerId(podOwner?.uid);
const title = `Pod ${selectedPod.getName()}`;
this.createLogsTab(title, {
pods: pods.length ? pods : [selectedPod],
selectedPod,
selectedContainer
});
}
createWorkloadTab({ workload }: WorkloadLogsTabData): void {
const pods = podsStore.getPodsByOwnerId(workload.getId());
if (!pods.length) return;
const selectedPod = pods[0];
const selectedContainer = selectedPod.getAllContainers()[0];
const title = `${workload.kind} ${selectedPod.getName()}`;
this.createLogsTab(title, {
pods,
selectedPod,
selectedContainer
});
}
renameTab(tabId: string) {
const { selectedPod } = this.getData(tabId);
dockStore.renameTab(tabId, `Pod ${selectedPod.metadata.name}`);
}
private createDockTab(tabParams: Partial<IDockTab>) {
dockStore.createTab({
kind: TabKind.POD_LOGS,
...tabParams
}, false);
}
private createLogsTab(title: string, data: LogTabData) {
const id = uniqueId("log-tab-");
this.createDockTab({ id, title });
this.setData(id, {
...data,
showTimestamps: false,
previous: false
});
}
private updateTabsData() {
this.data.forEach((tabData, tabId) => {
const pod = new Pod(tabData.selectedPod);
const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid);
const isSelectedPodInList = pods.find(item => item.getId() == pod.getId());
const selectedPod = isSelectedPodInList ? pod : pods[0];
const selectedContainer = isSelectedPodInList ? tabData.selectedContainer : pod.getAllContainers()[0];
if (pods.length) {
this.setData(tabId, {
...tabData,
selectedPod,
selectedContainer,
pods
});
this.renameTab(tabId);
} else {
this.closeTab(tabId);
}
});
}
private closeTab(tabId: string) {
this.clearData(tabId);
dockStore.closeTab(tabId);
}
}
export const logTabStore = new LogTabStore();
export function isLogsTab(tab: IDockTab) {
return tab && tab.kind === TabKind.POD_LOGS;
}
+11 -77
View File
@@ -1,27 +1,16 @@
import { autorun, computed, observable, reaction } from "mobx";
import { Pod, IPodContainer, podsApi, IPodLogsQuery } from "../../api/endpoints";
import { autorun, computed, observable } from "mobx";
import { IPodLogsQuery, Pod, podsApi } from "../../api/endpoints";
import { autobind, interval } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
import { searchStore } from "../../../common/search-store";
import { dockStore, TabId } from "./dock.store";
import { isLogsTab, logTabStore } from "./log-tab.store";
export interface IPodLogsData {
pod: Pod;
selectedContainer: IPodContainer
containers: IPodContainer[]
initContainers: IPodContainer[]
showTimestamps: boolean
previous: boolean
}
type TabId = string;
type PodLogLine = string;
// Number for log lines to load
export const logRange = 500;
const logLinesToLoad = 500;
@autobind()
export class LogStore extends DockTabStore<IPodLogsData> {
export class LogStore {
private refresher = interval(10, () => {
const id = dockStore.selectedTabId;
@@ -30,12 +19,8 @@ export class LogStore extends DockTabStore<IPodLogsData> {
});
@observable podLogs = observable.map<TabId, PodLogLine[]>();
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
constructor() {
super({
storageName: "pod_logs"
});
autorun(() => {
const { selectedTab, isOpen } = dockStore;
@@ -45,15 +30,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
this.refresher.stop();
}
}, { delay: 500 });
reaction(() => this.podLogs.get(dockStore.selectedTabId), () => {
this.setNewLogSince(dockStore.selectedTabId);
});
reaction(() => dockStore.selectedTabId, () => {
// Clear search query on tab change
searchStore.reset();
});
}
/**
@@ -66,7 +42,7 @@ export class LogStore extends DockTabStore<IPodLogsData> {
load = async (tabId: TabId) => {
try {
const logs = await this.loadLogs(tabId, {
tailLines: this.lines + logRange
tailLines: this.lines + logLinesToLoad
});
this.refresher.start();
@@ -107,9 +83,9 @@ export class LogStore extends DockTabStore<IPodLogsData> {
* @returns {Promise} A fetch request promise
*/
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>) => {
const data = this.getData(tabId);
const data = logTabStore.getData(tabId);
const { selectedContainer, previous } = data;
const pod = new Pod(data.pod);
const pod = new Pod(data.selectedPod);
const namespace = pod.getNs();
const name = pod.getName();
@@ -127,17 +103,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
});
};
/**
* Sets newLogSince separator timestamp to split old logs from new ones
* @param tabId
*/
setNewLogSince(tabId: TabId) {
if (!this.podLogs.has(tabId) || !this.podLogs.get(tabId).length || this.newLogSince.has(tabId)) return;
const timestamp = this.getLastSinceTime(tabId);
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
}
/**
* Converts logs into a string array
* @returns {number} Length of log lines
@@ -196,37 +161,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
clearLogs(tabId: TabId) {
this.podLogs.delete(tabId);
}
clearData(tabId: TabId) {
this.data.delete(tabId);
this.clearLogs(tabId);
}
}
export const podLogsStore = new LogStore();
export function createPodLogsTab(data: IPodLogsData, tabParams: Partial<IDockTab> = {}) {
const podId = data.pod.getId();
let tab = dockStore.getTabById(podId);
if (tab) {
dockStore.open();
dockStore.selectTab(tab.id);
return;
}
// If no existent tab found
tab = dockStore.createTab({
id: podId,
kind: TabKind.POD_LOGS,
title: data.pod.getName(),
...tabParams
}, false);
podLogsStore.setData(tab.id, data);
return tab;
}
export function isLogsTab(tab: IDockTab) {
return tab && tab.kind === TabKind.POD_LOGS;
}
export const logStore = new LogStore();
+11 -9
View File
@@ -8,9 +8,10 @@ import { IDockTab } from "./dock.store";
import { InfoPanel } from "./info-panel";
import { LogResourceSelector } from "./log-resource-selector";
import { LogList } from "./log-list";
import { IPodLogsData, podLogsStore } from "./log.store";
import { logStore } from "./log.store";
import { LogSearch } from "./log-search";
import { LogControls } from "./log-controls";
import { LogTabData, logTabStore } from "./log-tab.store";
interface Props {
className?: string
@@ -30,7 +31,7 @@ export class Logs extends React.Component<Props> {
}
get tabData() {
return podLogsStore.getData(this.tabId);
return logTabStore.getData(this.tabId);
}
get tabId() {
@@ -38,18 +39,18 @@ export class Logs extends React.Component<Props> {
}
@autobind()
save(data: Partial<IPodLogsData>) {
podLogsStore.setData(this.tabId, { ...this.tabData, ...data });
save(data: Partial<LogTabData>) {
logTabStore.setData(this.tabId, { ...this.tabData, ...data });
}
load = async () => {
this.isLoading = true;
await podLogsStore.load(this.tabId);
await logStore.load(this.tabId);
this.isLoading = false;
};
reload = async () => {
podLogsStore.clearLogs(this.tabId);
logStore.clearLogs(this.tabId);
await this.load();
};
@@ -82,11 +83,12 @@ export class Logs extends React.Component<Props> {
}
renderResourceSelector() {
const logs = podLogsStore.logs;
const searchLogs = this.tabData.showTimestamps ? logs : podLogsStore.logsWithoutTimestamps;
const logs = logStore.logs;
const searchLogs = this.tabData.showTimestamps ? logs : logStore.logsWithoutTimestamps;
const controls = (
<div className="flex gaps">
<LogResourceSelector
tabId={this.tabId}
tabData={this.tabData}
save={this.save}
reload={this.reload}
@@ -112,7 +114,7 @@ export class Logs extends React.Component<Props> {
}
render() {
const logs = podLogsStore.logs;
const logs = logStore.logs;
return (
<div className="PodLogs flex column">
+4
View File
@@ -47,6 +47,10 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
}
}
getById(id: string) {
return this.items.find(item => item.getId() === id);
}
getByName(name: string, namespace?: string): T {
return this.items.find(item => {
return item.getName() === name && (