|
|
@@ -1,129 +1,65 @@
|
|
|
-import React, { useContext, useEffect, useState } from "react";
|
|
|
+import React, { useMemo, useState } from "react";
|
|
|
import styled from "styled-components";
|
|
|
-
|
|
|
-import loadingSrc from "assets/loading.gif";
|
|
|
-import { Context } from "shared/Context";
|
|
|
-import { ChartType } from "../../../../../shared/types";
|
|
|
-import api from "../../../../../shared/api";
|
|
|
-import EventCard from "./EventCard";
|
|
|
+import EventCard from "components/events/EventCard";
|
|
|
import Loading from "components/Loading";
|
|
|
-import EventDetail from "./EventDetail";
|
|
|
-
|
|
|
-export type Event = {
|
|
|
- event_id: string;
|
|
|
- index: number;
|
|
|
- info: string;
|
|
|
- name: string;
|
|
|
- status: number;
|
|
|
- time: number;
|
|
|
-};
|
|
|
-
|
|
|
-export type EventContainer = {
|
|
|
- events: Event[];
|
|
|
- name: string;
|
|
|
- started_at: number;
|
|
|
-};
|
|
|
-
|
|
|
-type Props = {
|
|
|
- currentChart: ChartType;
|
|
|
-};
|
|
|
-
|
|
|
-const REFRESH_TIME = 15000;
|
|
|
-
|
|
|
-const EventsTab: React.FunctionComponent<Props> = (props) => {
|
|
|
- const { currentCluster, currentProject } = useContext(Context);
|
|
|
- const [isLoading, setIsLoading] = useState(true);
|
|
|
- const [isError, setIsError] = useState(false);
|
|
|
- const [shouldRequest, setShouldRequest] = useState(true);
|
|
|
- const [eventData, setEventData] = useState<EventContainer[]>([]); // most recent event is last
|
|
|
- const [selectedEvent, setSelectedEvent] = useState<number | null>(null);
|
|
|
-
|
|
|
- // sort by time, ensure sequences are monotonically increasing by time, collapse by id
|
|
|
- const filterData = (data: Event[]) => {
|
|
|
- data = data.sort((a, b) => a.time - b.time);
|
|
|
-
|
|
|
- if (data.length == 0) return;
|
|
|
-
|
|
|
- let seq: Event[][] = [];
|
|
|
- let cur: Event[] = [data[0]];
|
|
|
-
|
|
|
- for (let i = 1; i < data.length; ++i) {
|
|
|
- if (data[i].index < data[i - 1].index) {
|
|
|
- seq.push(cur);
|
|
|
- cur = [];
|
|
|
- }
|
|
|
- cur.push(data[i]);
|
|
|
+import InfiniteScroll from "react-infinite-scroll-component";
|
|
|
+import Dropdown from "components/Dropdown";
|
|
|
+import { useKubeEvents } from "components/events/useEvents";
|
|
|
+import { ChartType } from "shared/types";
|
|
|
+import _, { isObject } from "lodash";
|
|
|
+import SubEventsList from "components/events/SubEventsList";
|
|
|
+
|
|
|
+const availableResourceTypes = [
|
|
|
+ { label: "Pods", value: "pod" },
|
|
|
+ { label: "HPA", value: "hpa" },
|
|
|
+];
|
|
|
+
|
|
|
+const EventsTab: React.FC<{
|
|
|
+ controllers: Record<string, Record<string, any>>;
|
|
|
+}> = (props) => {
|
|
|
+ const { controllers } = props;
|
|
|
+ const [resourceType, setResourceType] = useState(availableResourceTypes[0]);
|
|
|
+ const [currentEvent, setCurrentEvent] = useState(null);
|
|
|
+
|
|
|
+ const [selectedControllerKey, setSelectedControllerKey] = useState(null);
|
|
|
+
|
|
|
+ const controllerOptions = useMemo(() => {
|
|
|
+ if (typeof controllers !== "object") {
|
|
|
+ return [];
|
|
|
}
|
|
|
- if (cur) seq.push(cur);
|
|
|
-
|
|
|
- let ret: EventContainer[] = [];
|
|
|
- seq.forEach((j) => {
|
|
|
- j.push({
|
|
|
- event_id: "",
|
|
|
- index: 0,
|
|
|
- info: "",
|
|
|
- name: "",
|
|
|
- status: 0,
|
|
|
- time: 0,
|
|
|
- });
|
|
|
|
|
|
- let fin: EventContainer = {
|
|
|
- events: [],
|
|
|
- name: "Deployment",
|
|
|
- started_at: j[0].time,
|
|
|
- };
|
|
|
- for (let i = 0; i < j.length - 1; ++i) {
|
|
|
- if (j[i].event_id != j[i + 1].event_id) {
|
|
|
- fin.events.push(j[i]);
|
|
|
- }
|
|
|
- }
|
|
|
- ret.push(fin);
|
|
|
- });
|
|
|
-
|
|
|
- setEventData(ret);
|
|
|
- };
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- const getData = () => {
|
|
|
- if (!shouldRequest) return;
|
|
|
- setShouldRequest(false);
|
|
|
- api
|
|
|
- .getReleaseSteps(
|
|
|
- "<token>",
|
|
|
- {},
|
|
|
- {
|
|
|
- cluster_id: currentCluster.id,
|
|
|
- namespace: props.currentChart.namespace,
|
|
|
- id: currentProject.id,
|
|
|
- name: props.currentChart.name,
|
|
|
- }
|
|
|
- )
|
|
|
- .then((data) => {
|
|
|
- setIsLoading(false);
|
|
|
- filterData(data.data);
|
|
|
- })
|
|
|
- .catch((err) => {
|
|
|
- setIsError(true);
|
|
|
- })
|
|
|
- .finally(() => {
|
|
|
- setShouldRequest(true);
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- getData();
|
|
|
- const id = window.setInterval(getData, REFRESH_TIME);
|
|
|
+ return Object.entries(controllers).map(([key, value]) => ({
|
|
|
+ label: value?.metadata?.name,
|
|
|
+ value: key,
|
|
|
+ }));
|
|
|
+ }, [controllers]);
|
|
|
|
|
|
- return () => {
|
|
|
- setIsLoading(true);
|
|
|
- window.clearInterval(id);
|
|
|
- };
|
|
|
- }, [currentProject, currentCluster, props.currentChart]);
|
|
|
+ const currentControllerOption = useMemo(() => {
|
|
|
+ return (
|
|
|
+ controllerOptions?.find((c) => c.value === selectedControllerKey) ||
|
|
|
+ controllerOptions[0]
|
|
|
+ );
|
|
|
+ }, [selectedControllerKey, controllerOptions]);
|
|
|
+
|
|
|
+ const selectedController = controllers[currentControllerOption?.value];
|
|
|
+
|
|
|
+ console.log(controllers, currentControllerOption);
|
|
|
+ const {
|
|
|
+ isLoading,
|
|
|
+ hasPorterAgent,
|
|
|
+ triggerInstall,
|
|
|
+ kubeEvents,
|
|
|
+ loadMoreEvents,
|
|
|
+ hasMore,
|
|
|
+ } = useKubeEvents(
|
|
|
+ resourceType.value as any,
|
|
|
+ selectedController?.metadata?.name,
|
|
|
+ selectedController?.kind
|
|
|
+ );
|
|
|
|
|
|
- if (isError) {
|
|
|
- return <Placeholder>Error loading events.</Placeholder>;
|
|
|
- }
|
|
|
+ const hasControllers = controllers && Object.keys(controllers)?.length;
|
|
|
|
|
|
- if (isLoading) {
|
|
|
+ if (isLoading || !hasControllers) {
|
|
|
return (
|
|
|
<Placeholder>
|
|
|
<Loading />
|
|
|
@@ -131,57 +67,105 @@ const EventsTab: React.FunctionComponent<Props> = (props) => {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- if (eventData.length === 0) {
|
|
|
+ if (!hasPorterAgent) {
|
|
|
return (
|
|
|
<Placeholder>
|
|
|
- <i className="material-icons">category</i>
|
|
|
- No application events found.
|
|
|
+ <div>
|
|
|
+ <Header>We coulnd't detect porter agent :(</Header>
|
|
|
+ In order to use the events tab you should install the porter agent!
|
|
|
+ <InstallPorterAgentButton onClick={() => triggerInstall()}>
|
|
|
+ <i className="material-icons">add</i> Install porter agent
|
|
|
+ </InstallPorterAgentButton>
|
|
|
+ </div>
|
|
|
</Placeholder>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- if (selectedEvent !== null) {
|
|
|
+ if (currentEvent) {
|
|
|
return (
|
|
|
- <EventDetail
|
|
|
- container={eventData[selectedEvent]}
|
|
|
- resetSelection={() => {
|
|
|
- setSelectedEvent(null);
|
|
|
- return null;
|
|
|
- }}
|
|
|
+ <SubEventsList
|
|
|
+ event={currentEvent}
|
|
|
+ clearSelectedEvent={() => setCurrentEvent(null)}
|
|
|
/>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
- <EventsGrid>
|
|
|
- {eventData
|
|
|
- .slice(0)
|
|
|
- .reverse()
|
|
|
- .map((dat, i) => {
|
|
|
- console.log(dat.started_at);
|
|
|
- return (
|
|
|
- <React.Fragment key={dat.started_at}>
|
|
|
- <EventCard
|
|
|
- event={dat.events[dat.events.length - 1]}
|
|
|
- selectEvent={() => {
|
|
|
- setSelectedEvent(eventData.length - i - 1);
|
|
|
- }}
|
|
|
- overrideName={"Deployment"}
|
|
|
- />
|
|
|
- </React.Fragment>
|
|
|
- );
|
|
|
- })}
|
|
|
- </EventsGrid>
|
|
|
+ <EventsPageWrapper>
|
|
|
+ <ControlRow>
|
|
|
+ <Dropdown
|
|
|
+ selectedOption={resourceType}
|
|
|
+ options={availableResourceTypes}
|
|
|
+ onSelect={(o) => setResourceType({ ...o, value: o.value as string })}
|
|
|
+ />
|
|
|
+ <RightFilters>
|
|
|
+ <Dropdown
|
|
|
+ selectedOption={currentControllerOption}
|
|
|
+ options={controllerOptions}
|
|
|
+ onSelect={(o) => setSelectedControllerKey(o?.value)}
|
|
|
+ />
|
|
|
+ </RightFilters>
|
|
|
+ </ControlRow>
|
|
|
+
|
|
|
+ <InfiniteScroll
|
|
|
+ dataLength={kubeEvents.length}
|
|
|
+ next={loadMoreEvents}
|
|
|
+ hasMore={hasMore}
|
|
|
+ loader={<h4>Loading...</h4>}
|
|
|
+ scrollableTarget="HomeViewWrapper"
|
|
|
+ endMessage={
|
|
|
+ <h4>No events were found for the resource type you specified</h4>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <EventsGrid>
|
|
|
+ {kubeEvents.map((event, i) => {
|
|
|
+ return (
|
|
|
+ <React.Fragment key={i}>
|
|
|
+ <EventCard
|
|
|
+ event={event as any}
|
|
|
+ selectEvent={() => {
|
|
|
+ setCurrentEvent(event);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </React.Fragment>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </EventsGrid>
|
|
|
+ </InfiniteScroll>
|
|
|
+ </EventsPageWrapper>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default EventsTab;
|
|
|
|
|
|
+const RightFilters = styled.div`
|
|
|
+ display: flex;
|
|
|
+ > div {
|
|
|
+ :not(:last-child) {
|
|
|
+ margin-right: 15px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
+const ControlRow = styled.div`
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 35px;
|
|
|
+ padding-left: 0px;
|
|
|
+`;
|
|
|
+
|
|
|
const EventsPageWrapper = styled.div`
|
|
|
margin-top: 35px;
|
|
|
padding-bottom: 80px;
|
|
|
`;
|
|
|
|
|
|
+const EventsGrid = styled.div`
|
|
|
+ display: grid;
|
|
|
+ grid-row-gap: 15px;
|
|
|
+ grid-template-columns: 1;
|
|
|
+`;
|
|
|
+
|
|
|
const InstallPorterAgentButton = styled.button`
|
|
|
display: flex;
|
|
|
flex-direction: row;
|
|
|
@@ -205,14 +189,12 @@ const InstallPorterAgentButton = styled.button`
|
|
|
box-shadow: 0 5px 8px 0px #00000010;
|
|
|
cursor: ${(props: { disabled?: boolean }) =>
|
|
|
props.disabled ? "not-allowed" : "pointer"};
|
|
|
-
|
|
|
background: ${(props: { disabled?: boolean }) =>
|
|
|
props.disabled ? "#aaaabbee" : "#616FEEcc"};
|
|
|
:hover {
|
|
|
background: ${(props: { disabled?: boolean }) =>
|
|
|
props.disabled ? "" : "#505edddd"};
|
|
|
}
|
|
|
-
|
|
|
> i {
|
|
|
color: white;
|
|
|
width: 18px;
|
|
|
@@ -228,19 +210,16 @@ const InstallPorterAgentButton = styled.button`
|
|
|
`;
|
|
|
|
|
|
const Placeholder = styled.div`
|
|
|
+ min-height: 200px;
|
|
|
+ height: 20vh;
|
|
|
+ padding: 30px;
|
|
|
+ padding-bottom: 90px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #ffffff44;
|
|
|
width: 100%;
|
|
|
- min-height: 300px;
|
|
|
- height: 40vh;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- color: #ffffff44;
|
|
|
- font-size: 14px;
|
|
|
-
|
|
|
- > i {
|
|
|
- font-size: 18px;
|
|
|
- margin-right: 10px;
|
|
|
- }
|
|
|
`;
|
|
|
|
|
|
const Header = styled.div`
|
|
|
@@ -249,16 +228,3 @@ const Header = styled.div`
|
|
|
font-size: 16px;
|
|
|
margin-bottom: 15px;
|
|
|
`;
|
|
|
-
|
|
|
-const Spinner = styled.img`
|
|
|
- width: 15px;
|
|
|
- height: 15px;
|
|
|
- margin-right: 12px;
|
|
|
- margin-bottom: -2px;
|
|
|
-`;
|
|
|
-
|
|
|
-const EventsGrid = styled.div`
|
|
|
- display: grid;
|
|
|
- grid-row-gap: 15px;
|
|
|
- grid-template-columns: 1;
|
|
|
-`;
|