فهرست منبع

Merge branch '0.8.0-pod-events-managment' of https://github.com/porter-dev/porter into 0.8.0-pod-events-managment

merge remote
Alexander Belanger 4 سال پیش
والد
کامیت
d398e13939

+ 154 - 0
dashboard/src/components/Dropdown.tsx

@@ -0,0 +1,154 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+
+type Option = {
+  value: unknown;
+  label: string;
+};
+
+type DropdownProps = {
+  options: Array<Option>;
+  onSelect: (selectedOption: Option) => unknown;
+  selectLabel?: (currentLabel: string) => void;
+  selectValue?: (currentValue: any) => void;
+};
+
+const Dropdown: React.FunctionComponent<DropdownProps> = ({
+  options,
+  selectLabel,
+  selectValue,
+  onSelect,
+}) => {
+  const [isDropdownExpanded, setIsDropdownExpanded] = useState(false);
+  const [selectedOption, setSelectedOption] = useState<Option>(options[0]);
+
+  const handleSelectOption = (option: Option) => {
+    setSelectedOption(option);
+    onSelect(option);
+    typeof selectLabel === "function" && selectLabel(option.label);
+    typeof selectValue === "function" && selectValue(option.value);
+  };
+
+  const renderDropdown = () => {
+    if (isDropdownExpanded) {
+      return (
+        <>
+          <DropdownOverlay onClick={() => setIsDropdownExpanded(false)} />
+          <OptionWrapper
+            dropdownWidth="230px"
+            dropdownMaxHeight="200px"
+            onClick={() => setIsDropdownExpanded(false)}
+          >
+            {renderOptionList()}
+          </OptionWrapper>
+        </>
+      );
+    }
+  };
+
+  const renderOptionList = () => {
+    return options.map((option, i, originalArray) => {
+      return (
+        <Option
+          key={i}
+          selected={option.label === selectedOption.label}
+          onClick={() => handleSelectOption(option)}
+          lastItem={i === originalArray.length - 1}
+        >
+          {option.label}
+        </Option>
+      );
+    });
+  };
+
+  return (
+    <DropdownSelector
+      onClick={() => setIsDropdownExpanded(!isDropdownExpanded)}
+    >
+      <DropdownLabel>{selectedOption?.label}</DropdownLabel>
+      <i className="material-icons">arrow_drop_down</i>
+      {renderDropdown()}
+    </DropdownSelector>
+  );
+};
+
+export default Dropdown;
+
+const DropdownSelector = styled.div`
+  font-size: 13px;
+  font-weight: 500;
+  position: relative;
+  color: #ffffff;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  border-radius: 5px;
+  :hover {
+    > i {
+      background: #ffffff22;
+    }
+  }
+
+  > i {
+    border-radius: 20px;
+    font-size: 20px;
+    margin-left: 10px;
+  }
+`;
+
+const DropdownLabel = styled.div`
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  max-width: 200px;
+`;
+
+const DropdownOverlay = styled.div`
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  left: 0px;
+  top: 0px;
+  cursor: default;
+`;
+
+const OptionWrapper = styled.div`
+  position: absolute;
+  left: 0;
+  top: calc(100% + 10px);
+  background: #26282f;
+  width: ${(props: { dropdownWidth: string; dropdownMaxHeight: string }) =>
+    props.dropdownWidth};
+  max-height: ${(props: { dropdownWidth: string; dropdownMaxHeight: string }) =>
+    props.dropdownMaxHeight || "300px"};
+  border-radius: 3px;
+  z-index: 999;
+  overflow-y: auto;
+  margin-bottom: 20px;
+  box-shadow: 0px 4px 10px 0px #00000088;
+`;
+
+const Option = styled.div`
+  width: 100%;
+  border-top: 1px solid #00000000;
+  border-bottom: 1px solid
+    ${(props: { selected: boolean; lastItem: boolean }) =>
+      props.lastItem ? "#ffffff00" : "#ffffff15"};
+  height: 37px;
+  font-size: 13px;
+  padding-top: 9px;
+  align-items: center;
+  padding-left: 15px;
+  cursor: pointer;
+  padding-right: 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: ${(props: { selected: boolean; lastItem: boolean }) =>
+    props.selected ? "#ffffff11" : ""};
+
+  :hover {
+    background: #ffffff22;
+  }
+`;

+ 89 - 6
dashboard/src/components/Card.tsx → dashboard/src/components/EventCard.tsx

@@ -1,9 +1,10 @@
-import React from "react";
+import React, { useState } from "react";
 import styled from "styled-components";
 import { Event } from "main/home/cluster-dashboard/expanded-chart/events/EventsTab";
 
 type CardProps = {
   event: Event;
+  selectEvent: (id: number) => void;
 };
 const getReadableDate = (s: string) => {
   let ts = new Date(s);
@@ -16,7 +17,12 @@ const getReadableDate = (s: string) => {
 };
 
 // Rename to Event Card
-const Card: React.FunctionComponent<CardProps> = ({ event }) => {
+const EventCard: React.FunctionComponent<CardProps> = ({
+  event,
+  selectEvent,
+}) => {
+  const [showTooltip, setShowTooltip] = useState(false);
+
   return (
     <StyledCard>
       <ContentContainer>
@@ -34,15 +40,29 @@ const Card: React.FunctionComponent<CardProps> = ({ event }) => {
           </EventReason>
         </EventInformation>
       </ContentContainer>
-      <ActionContainer>
-        <span className="material-icons-outlined">access_time</span>
-        <span>{getReadableDate(event.timestamp)}</span>
+      <ActionContainer hasOneChild={event.event_type === "normal"}>
+        {event.event_type === "critical" && (
+          <HistoryButton
+            onClick={() => selectEvent(event.id)}
+            onMouseEnter={() => setShowTooltip(true)}
+            onMouseLeave={() => setShowTooltip(false)}
+          >
+            <span className="material-icons-outlined">manage_search</span>
+            {showTooltip && <Tooltip>Open logs</Tooltip>}
+          </HistoryButton>
+        )}
+        <TimestampContainer>
+          <TimestampIcon className="material-icons-outlined">
+            access_time
+          </TimestampIcon>
+          <span>{getReadableDate(event.timestamp)}</span>
+        </TimestampContainer>
       </ActionContainer>
     </StyledCard>
   );
 };
 
-export default Card;
+export default EventCard;
 
 const StyledCard = styled.div`
   background: #26282f;
@@ -114,4 +134,67 @@ const ActionContainer = styled.div`
   align-items: center;
   white-space: nowrap;
   height: 100%;
+  flex-direction: column;
+  justify-content: ${(props: { hasOneChild: boolean }) => {
+    return props.hasOneChild ? "flex-end" : "space-between";
+  }};
+`;
+
+const HistoryButton = styled.button`
+  position: relative;
+  border: none;
+  background: none;
+  color: white;
+  padding: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+  color: #ffffff44;
+  :hover {
+    background: #32343a;
+    cursor: pointer;
+  }
+`;
+
+const Tooltip = styled.div`
+  position: absolute;
+  left: 0px;
+  word-wrap: break-word;
+  top: 38px;
+  min-height: 18px;
+  padding: 5px 7px;
+  background: #272731;
+  z-index: 999;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  flex: 1;
+  color: white;
+  text-transform: none;
+  font-size: 12px;
+  font-family: "Work Sans", sans-serif;
+  outline: 1px solid #ffffff55;
+  opacity: 0;
+  animation: faded-in 0.2s 0.15s;
+  animation-fill-mode: forwards;
+  @keyframes faded-in {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+`;
+
+const TimestampContainer = styled.div`
+  display: flex;
+  white-space: nowrap;
+  align-items: center;
+  justify-self: flex-end;
+`;
+
+const TimestampIcon = styled.span`
+  margin-right: 5px;
 `;

+ 12 - 0
dashboard/src/components/EventLogs.tsx

@@ -0,0 +1,12 @@
+import { Event } from "main/home/cluster-dashboard/expanded-chart/events/EventsTab";
+import React from "react";
+
+type EventLogsProps = {
+  event: Event;
+};
+
+const EventLogs: React.FunctionComponent<EventLogsProps> = ({}) => {
+  return <div>Show logs</div>;
+};
+
+export default EventLogs;

+ 122 - 13
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventsTab.tsx

@@ -1,6 +1,9 @@
-import Card from "components/Card";
-import React from "react";
+import React, { useEffect, useState } from "react";
 import styled from "styled-components";
+import backArrow from "assets/back_arrow.png";
+import Dropdown from "components/Dropdown";
+import EventCard from "components/EventCard";
+import EventLogs from "components/EventLogs";
 
 const mockData = [
   {
@@ -23,7 +26,7 @@ const mockData = [
     cluster_id: 6,
     owner_name: "pod-test",
     owner_type: "deployment",
-    event_type: "critical",
+    event_type: "normal",
     resource_type: "pod",
     name: "pod-test-2",
     namespace: "default",
@@ -32,7 +35,7 @@ const mockData = [
     timestamp: "2021-06-30T21:48:23Z",
   },
   {
-    id: 2,
+    id: 3,
     project_id: 1,
     cluster_id: 6,
     owner_name: "pod-test",
@@ -46,7 +49,7 @@ const mockData = [
     timestamp: "2021-06-30T21:48:23Z",
   },
   {
-    id: 2,
+    id: 4,
     project_id: 1,
     cluster_id: 6,
     owner_name: "pod-test",
@@ -60,7 +63,7 @@ const mockData = [
     timestamp: "2021-06-30T21:48:23Z",
   },
   {
-    id: 2,
+    id: 5,
     project_id: 1,
     cluster_id: 6,
     owner_name: "pod-test",
@@ -74,7 +77,7 @@ const mockData = [
     timestamp: "2021-06-30T21:48:23Z",
   },
   {
-    id: 2,
+    id: 6,
     project_id: 1,
     cluster_id: 6,
     owner_name: "pod-test",
@@ -104,20 +107,126 @@ export type Event = {
   timestamp: string;
 };
 
-const EventsTab: React.FunctionComponent = () => {
+type EventsTabProps = {};
+
+const EventsTab: React.FunctionComponent<EventsTabProps> = () => {
+  const [eventList, setEventList] = useState<Event[]>([]);
+  const [selectedEvent, setSelectedEvent] = useState<Event>(null);
+  const [currentFilter, setCurrentFilter] = useState<string>("all");
+
+  useEffect(() => {
+    setTimeout(() => {
+      setEventList(
+        (mockData as Event[]).filter(
+          (e) => currentFilter === "all" || e.resource_type === currentFilter
+        )
+      );
+    }, 500);
+  }, [currentFilter]);
+
+  const selectEvent = (id: number) => {
+    const event = eventList.find((e) => e.id === id);
+    setSelectedEvent(event);
+  };
+
+  const clearSelectedEvent = () => {
+    setSelectedEvent(null);
+  };
+
+  const handleEventTypeSelection = (option: {
+    label: string;
+    value: string;
+  }) => {
+    console.log(option);
+    setCurrentFilter(option.value);
+  };
+
   return (
-    <EventsGrid>
-      {mockData.map((event) => {
-        return <Card event={event as Event} />;
-      })}
-    </EventsGrid>
+    <NamespaceListWrapper>
+      {!selectedEvent && (
+        <>
+          <ControlRow>
+            <div>
+              <Dropdown
+                options={[
+                  { label: "All", value: "all" },
+                  { label: "Pods", value: "pod" },
+                  { label: "HPA", value: "HPA" },
+                ]}
+                onSelect={handleEventTypeSelection}
+              />
+            </div>
+          </ControlRow>
+          <EventsGrid>
+            {eventList.map((event) => {
+              return (
+                <EventCard
+                  key={event.id}
+                  event={event}
+                  selectEvent={selectEvent}
+                />
+              );
+            })}
+          </EventsGrid>
+        </>
+      )}
+      {selectedEvent && (
+        <>
+          <ControlRow>
+            <div>
+              <BackButton onClick={clearSelectedEvent}>
+                <BackButtonImg src={backArrow} />
+              </BackButton>
+            </div>
+          </ControlRow>
+          <EventLogs event={selectedEvent} />
+        </>
+      )}
+    </NamespaceListWrapper>
   );
 };
 
 export default EventsTab;
 
+const NamespaceListWrapper = styled.div`
+  margin-top: 35px;
+  padding-bottom: 80px;
+`;
+
+const ControlRow = styled.div`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 35px;
+  padding-left: 0px;
+`;
+
 const EventsGrid = styled.div`
   display: grid;
   grid-row-gap: 15px;
   grid-template-columns: 1;
 `;
+
+const BackButton = styled.div`
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;