Feroze Mohideen пре 3 година
родитељ
комит
6d56476e4b

+ 1 - 1
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -729,7 +729,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
           </>
         );
       case "logs":
-        return <LogSection currentChart={appData.chart} services={services} />;
+        return <LogSection currentChart={appData.chart} services={services} appName={appData.app.name} />;
       case "metrics":
         return <MetricsSection currentChart={appData.chart} />;
       case "debug":

+ 0 - 85
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogFilter.tsx

@@ -1,85 +0,0 @@
-import React, { useEffect, useState, useRef } from "react";
-
-import styled from "styled-components";
-import arrow from "assets/arrow-down.svg";
-import filterOutline from "assets/filter-outline-icon.svg";
-import filterOutlineWhite from "assets/filter-outline-white.svg";
-import { GenericLogFilter, LogFilterName } from "./types";
-import Icon from "components/porter/Icon";
-import Spacer from "components/porter/Spacer";
-import LogFilterComponent from "./LogFilterComponent";
-
-type Props = {
-    icon?: any;
-    options: { value: any; label: string }[];
-    selected: any;
-    setSelected: any;
-    noMargin?: boolean;
-    dropdownAlignRight?: boolean;
-    filters: GenericLogFilter[];
-    selectedFilterValues: Record<LogFilterName, string>;
-};
-
-const LogFilter: React.FC<Props> = (props) => {
-
-    const getIcon = () => {
-        if (props.filters.every((filter) => GenericLogFilter.isDefault(filter, props.selectedFilterValues[filter.name]))) {
-            return filterOutline;
-        }
-        return filterOutlineWhite;
-    }
-
-    const renderFilters = () => {
-        return props.filters.map((filter, i) => {
-            return <>
-                <LogFilterComponent
-                    key={i}
-                    options={[filter.default, ...filter.options]}
-                    name={filter.displayName}
-                />
-                <Spacer inline x={0.5} />
-            </>
-        })
-    }
-
-    return (
-        <Relative>
-            <StyledRadioFilter>
-                <Icon src={getIcon()} height={"16px"} />
-                <Spacer inline x={1} />
-                <Bar />
-                <Spacer inline x={1} />
-                {renderFilters()}
-            </StyledRadioFilter>
-            {/* {renderDropdown()} */}
-        </Relative>
-    );
-};
-
-export default LogFilter;
-
-const Bar = styled.div`
-  width: 1px;
-  height: calc(18px);
-  background: #494b4f;
-`;
-
-const Relative = styled.div`
-  position: relative;
-`;
-
-const StyledRadioFilter = styled.div<{ noMargin?: boolean }>`
-  height: 40px;
-  font-size: 13px;
-  position: relative;
-  padding: 10px;
-  background: ${(props) => props.theme.fg};
-  border-radius: 5px;
-  display: flex;
-  align-items: center;
-  cursor: pointer;
-  border: 1px solid #494b4f;
-  :hover {
-    border: 1px solid #7a7b80;
-  }
-`;

+ 13 - 13
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogFilterComponent.tsx

@@ -1,28 +1,29 @@
 import Text from "components/porter/Text";
-import React, { useEffect } from "react";
+import React from "react";
 import styled from "styled-components";
-import { GenericFilterOption } from "./types";
+import { GenericLogFilter } from "./types";
 import Spacer from "components/porter/Spacer";
 import Select from "components/porter/Select";
 
 type Props = {
-    name: string;
-    options: GenericFilterOption[];
+    filter: GenericLogFilter;
+    selectedValue: string;
 };
 
 const LogFilterComponent: React.FC<Props> = ({
-    options,
-    name,
+    filter,
+    selectedValue,
 }) => {
-    useEffect(() => {
-        // Do something
-    }, []);
-
     return (
         <StyledLogFilterComponent>
-            <Text>{name}</Text>
+            <Text>{filter.displayName}</Text>
             <Spacer inline x={0.5} />
-            <Select options={options} height={"30px"} />
+            <Select
+                options={[filter.default, ...filter.options]}
+                height={"30px"}
+                value={selectedValue}
+                setValue={filter.setValue}
+            />
         </StyledLogFilterComponent>
     );
 };
@@ -32,7 +33,6 @@ export default LogFilterComponent;
 const StyledLogFilterComponent = styled.div`
     display: flex;
     align-items: center;
-    width: 100%;
     animation: fadeIn 0.3s 0s;
     @keyframes fadeIn {
     from {

+ 72 - 0
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogFilterContainer.tsx

@@ -0,0 +1,72 @@
+import React from "react";
+
+import styled from "styled-components";
+import filterOutline from "assets/filter-outline-icon.svg";
+import filterOutlineWhite from "assets/filter-outline-white.svg";
+import { GenericLogFilter, LogFilterName } from "./types";
+import Icon from "components/porter/Icon";
+import Spacer from "components/porter/Spacer";
+import LogFilterComponent from "./LogFilterComponent";
+
+type Props = {
+    filters: GenericLogFilter[];
+    selectedFilterValues: Record<LogFilterName, string>;
+};
+
+const LogFilterContainer: React.FC<Props> = (props) => {
+    const getIcon = () => {
+        if (props.filters.every((filter) => GenericLogFilter.isDefault(filter, props.selectedFilterValues[filter.name]))) {
+            return filterOutline;
+        }
+        return filterOutlineWhite;
+    }
+
+    const renderFilters = () => {
+        return (
+            <FiltersContainer>
+                {props.filters.map((filter, i) => {
+                    return <LogFilterComponent
+                        key={i}
+                        filter={filter}
+                        selectedValue={props.selectedFilterValues[filter.name]}
+                    />
+                })}
+            </FiltersContainer>
+        )
+    }
+
+    return (
+        <StyledLogFilterContainer>
+            <Icon src={getIcon()} height={"16px"} />
+            <Spacer inline x={1} />
+            <Bar />
+            <Spacer inline x={1} />
+            {renderFilters()}
+        </StyledLogFilterContainer>
+    );
+};
+
+export default LogFilterContainer;
+
+const Bar = styled.div`
+  width: 1px;
+  height: calc(18px);
+  background: #494b4f;
+`;
+
+const StyledLogFilterContainer = styled.div`
+  font-size: 13px;
+  padding: 10px;
+  background: ${(props) => props.theme.fg};
+  border-radius: 5px;
+  display: flex;
+  align-items: center;
+  border: 1px solid #494b4f;
+  width: fit-content;
+`;
+
+const FiltersContainer = styled.div`
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+`

+ 39 - 92
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogSection.tsx

@@ -9,8 +9,6 @@ import React, {
 import styled from "styled-components";
 
 import spinner from "assets/loading.gif";
-import filterOutline from "assets/filter-outline.svg";
-import filterOutlineWhite from "assets/filter-outline-white.svg";
 import { Context } from "shared/Context";
 import api from "shared/api";
 import { useLogs } from "./utils";
@@ -28,7 +26,7 @@ import Spacer from "components/porter/Spacer";
 import Container from "components/porter/Container";
 import Button from "components/porter/Button";
 import { Service } from "../../new-app-flow/serviceTypes";
-import LogFilter from "./LogFilter";
+import LogFilterContainer from "./LogFilterContainer";
 import StyledLogs from "./StyledLogs";
 
 type Props = {
@@ -39,6 +37,7 @@ type Props = {
     endTime?: Dayjs;
   };
   showFilter?: boolean;
+  appName: string;
 };
 
 type PodFilter = {
@@ -50,6 +49,7 @@ const LogSection: React.FC<Props> = ({
   currentChart,
   services,
   timeRange,
+  appName,
   showFilter = true,
 }) => {
   const scrollToBottomRef = useRef<HTMLDivElement | undefined>(undefined);
@@ -70,15 +70,15 @@ const LogSection: React.FC<Props> = ({
   const [isLoading, setIsLoading] = useState(true);
   const [logsError, setLogsError] = useState<string | undefined>(undefined);
   const [selectedFilterValues, setSelectedFilterValues] = useState<Record<LogFilterName, string>>({
-    revision: "all",
-    output_stream: "all",
-    pod_name: "all",
+    revision: GenericLogFilter.getDefaultOption("revision").value,
+    output_stream: GenericLogFilter.getDefaultOption("output_stream").value,
+    pod_name: GenericLogFilter.getDefaultOption("pod_name").value,
   });
 
   const createVersionOptions = (number: number) => {
     return Array.from({ length: number }, (_, index) => {
       const version = index + 1;
-      const label = `Version ${version}`;
+      const label = version === number ? `Version ${version} (latest)` : `Version ${version}`;
       const value = version.toString();
       return GenericFilterOption.of(label, value);
     }).reverse().slice(0, 3);
@@ -88,41 +88,40 @@ const LogSection: React.FC<Props> = ({
     {
       name: "pod_name",
       displayName: "Service",
-      default: GenericFilterOption.of("All", "all"),
+      default: GenericLogFilter.getDefaultOption("pod_name"),
       options: services?.map(s => {
-        return GenericFilterOption.of(s.name, `${currentChart?.name}-${s.name}-${s.type == "worker" ? "wkr" : s.type}`)
+        return GenericFilterOption.of(s.name, `${s.name}-${s.type == "worker" ? "wkr" : s.type}`)
       }) ?? [],
-      setOption: (option: GenericFilterOption) => {
+      setValue: (value: string) => {
         setSelectedFilterValues((s) => ({
           ...s,
-          pod_name: option.value,
+          pod_name: value,
         }));
       }
     },
     {
       name: "revision",
       displayName: "Version",
-      default: GenericFilterOption.of("All", "all"),
+      default: GenericLogFilter.getDefaultOption("revision"),
       options: currentChart != null ? createVersionOptions(currentChart.version) : [],
-      setOption: (option: GenericFilterOption) => {
+      setValue: (value: string) => {
         setSelectedFilterValues((s) => ({
           ...s,
-          revision: option.value,
+          revision: value,
         }));
       }
     },
     {
       name: "output_stream",
       displayName: "Output Stream",
-      default: GenericFilterOption.of("All", "all"),
+      default: GenericLogFilter.getDefaultOption("output_stream"),
       options: [
-        GenericFilterOption.of("stdout", "stdout"),
         GenericFilterOption.of("stderr", "stderr"),
       ],
-      setOption: (option: GenericFilterOption) => {
+      setValue: (value: string) => {
         setSelectedFilterValues((s) => ({
           ...s,
-          output_stream: option.value,
+          output_stream: value,
         }));
       }
     },
@@ -136,12 +135,9 @@ const LogSection: React.FC<Props> = ({
     }, 5000);
   };
 
-  const namespace = currentChart == null ? "" : currentChart.namespace;
-
   const { logs, refresh, moveCursor, paginationInfo } = useLogs(
-    podFilter.podName,
-    podFilter.podType,
-    namespace,
+    selectedFilterValues,
+    currentChart == null ? "" : currentChart.namespace,
     enteredSearchText,
     notify,
     currentChart,
@@ -150,20 +146,6 @@ const LogSection: React.FC<Props> = ({
     timeRange,
   );
 
-  const refreshPodLogsValues = async () => {
-    if (currentChart == null || services == null) {
-      setPodFilterOpts([]);
-    } else {
-      const podList = services.map((service: Service) => {
-        return {
-          podName: service.name,
-          podType: service.type == "worker" ? "wkr" : service.type,
-        };
-      });
-      setPodFilterOpts(podList);
-    }
-  };
-
   useEffect(() => {
     if (!isLoading && scrollToBottomRef.current && scrollToBottomEnabled) {
       const scrollPosition = scrollToBottomRef.current.offsetTop + scrollToBottomRef.current.offsetHeight - window.innerHeight;
@@ -181,23 +163,23 @@ const LogSection: React.FC<Props> = ({
     }
   }, [selectedDate]);
 
-  const resetPodFilter = () => {
-    if (podFilter.podName != "" || podFilter.podType != "") {
-      setPodFilter({ podName: "", podType: "" });
+  const refreshPodLogsValues = async () => {
+    if (currentChart == null || services == null) {
+      setPodFilterOpts([]);
+    } else {
+      const podList = services.map((service: Service) => {
+        return {
+          podName: service.name,
+          podType: service.type == "worker" ? "wkr" : service.type,
+        };
+      });
+      setPodFilterOpts(podList);
     }
   };
 
-  const setPodFilterWithPodName = (podName: string) => {
-    if (podName == "All") {
-      resetPodFilter();
-      return;
-    }
-
-    const filtered = podFilterOpts.filter((pod) => pod.podName == podName);
-    if (filtered.length > 0) {
-      setPodFilter(filtered[0]);
-    } else {
-      resetPodFilter();
+  const resetPodFilter = () => {
+    if (podFilter.podName != "" || podFilter.podType != "") {
+      setPodFilter({ podName: "", podType: "" });
     }
   };
 
@@ -268,13 +250,7 @@ const LogSection: React.FC<Props> = ({
         <Spacer y={0.5} />
         {showFilter &&
           <>
-            <LogFilter
-              icon={
-                podFilter.podName === "" ? filterOutline : filterOutlineWhite
-              }
-              selected={podFilter.podName}
-              setSelected={setPodFilterWithPodName}
-              options={radioOptions}
+            <LogFilterContainer
               filters={filters}
               selectedFilterValues={selectedFilterValues}
             />
@@ -306,7 +282,11 @@ const LogSection: React.FC<Props> = ({
                 >
                   Load Previous
                 </LoadMoreButton>
-                <StyledLogs logs={logs} />
+                <StyledLogs
+                  logs={logs}
+                  appName={appName}
+                  filters={filters}
+                />
                 <LoadMoreButton
                   active={selectedDate && logs.length !== 0}
                   role="button"
@@ -579,39 +559,6 @@ const LoadMoreButton = styled.div<{ active: boolean }>`
   font-family: monospace;
 `;
 
-const ToggleOption = styled.div<{ selected: boolean; nudgeLeft?: boolean }>`
-  padding: 0 10px;
-  color: ${(props) => (props.selected ? "" : "#494b4f")};
-  border: 1px solid #494b4f;
-  height: 100%;
-  display: flex;
-  margin-left: ${(props) => (props.nudgeLeft ? "-1px" : "")};
-  align-items: center;
-  border-radius: ${(props) =>
-    props.nudgeLeft ? "0 5px 5px 0" : "5px 0 0 5px"};
-  :hover {
-    border: 1px solid #7a7b80;
-    z-index: 2;
-  }
-`;
-
-const ToggleButton = styled.div`
-  background: #26292e;
-  border-radius: 5px;
-  font-size: 13px;
-  height: 30px;
-  display: flex;
-  align-items: center;
-  cursor: pointer;
-`;
-
-const TimeIcon = styled.img<{ selected?: boolean }>`
-  width: 16px;
-  height: 16px;
-  z-index: 2;
-  opacity: ${(props) => (props.selected ? "" : "50%")};
-`;
-
 const NotificationWrapper = styled.div<{ active?: boolean }>`
   position: absolute;
   bottom: 10px;

+ 89 - 39
dashboard/src/main/home/app-dashboard/expanded-app/logs/StyledLogs.tsx

@@ -1,33 +1,68 @@
 import React from "react";
-import { PorterLog } from "./types";
+import { GenericLogFilter, PorterLog } from "./types";
 import styled from "styled-components";
 import Anser from "anser";
 import dayjs from "dayjs";
+import { getPodSelectorFromPodNameAndAppName, getServiceNameFromPodNameAndAppName, getVersionTagColor } from "./utils";
 
 
 type Props = {
     logs: PorterLog[];
+    appName: string;
+    filters: GenericLogFilter[];
 };
 
 const StyledLogs: React.FC<Props> = ({
-    logs
+    logs,
+    appName,
+    filters,
 }) => {
+    const renderFilterTagForLog = (filter: GenericLogFilter, log: PorterLog, index: number) => {
+        if (log.metadata == null) {
+            return null;
+        }
+        switch (filter.name) {
+            case "revision":
+                return (
+                    <LogInnerPill
+                        color={getVersionTagColor(log.metadata.revision)}
+                        key={index}
+                        onClick={() => filter.setValue(log.metadata.revision)}
+                    >
+                        {`Version: ${log.metadata.revision}`}
+                    </LogInnerPill>
+                )
+            case "pod_name":
+                return (
+                    <LogInnerPill
+                        color={"white"}
+                        key={index}
+                        onClick={() => filter.setValue(getPodSelectorFromPodNameAndAppName(log.metadata.pod_name, appName))}
+                    >
+                        {getServiceNameFromPodNameAndAppName(log.metadata.pod_name, appName)}
+                    </LogInnerPill>
+                )
+            default:
+                return null;
+        }
+    }
+
     return (
-        <>
+        <StyledLogsContainer>
             {logs.map((log, i) => {
                 return (
                     <Log key={[log.lineNumber, i].join(".")}>
-                        <span className="line-number">{log.lineNumber}.</span>
-                        <span className="line-timestamp">
-                            {log.timestamp
-                                ? dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")
-                                : "-"}
-                        </span>
-                        {log.metadata != null &&
-                            <LogInnerPill>
-                                {`Version: ${log.metadata.revision}`}
-                            </LogInnerPill>
-                        }
+                        <LogLabelsContainer includeLabels={log.metadata != null}>
+                            <LineNumber className="line-number">{log.lineNumber}.</LineNumber>
+                            <LineTimestamp className="line-timestamp">
+                                {log.timestamp
+                                    ? dayjs(log.timestamp).format("MM/DD HH:mm:ss")
+                                    : "-"}
+                            </LineTimestamp>
+                            {filters.map((filter, j) => {
+                                return renderFilterTagForLog(filter, log, j)
+                            })}
+                        </LogLabelsContainer>
                         <LogOuter key={[log.lineNumber, i].join(".")}>
                             {log.line?.map((ansi, j) => {
                                 if (ansi.clearLine) {
@@ -47,55 +82,70 @@ const StyledLogs: React.FC<Props> = ({
                     </Log>
                 );
             })}
-        </>
+        </StyledLogsContainer>
     );
 };
 
 export default StyledLogs;
 
-const Log = styled.div`
-  font-family: monospace;
-  user-select: text;
-  display: flex;
-  align-items: flex-end;
-  gap: 8px;
-  width: 100%;
-  & > * {
-    padding-block: 5px;
-  }
-  & > .line-timestamp {
+const StyledLogsContainer = styled.div`
+`;
+
+const LogLabelsContainer = styled.div<{ includeLabels: boolean }>`
+    min-width: ${props => props.includeLabels ? "390px" : "100px"}; 
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    gap: 10px;
+    
+`;
+
+const LineTimestamp = styled.span`
     height: 100%;
     color: #949effff;
     opacity: 0.5;
     font-family: monospace;
     min-width: fit-content;
-    padding-inline-end: 5px;
-  }
-  & > .line-number {
+`
+
+const LineNumber = styled.span`
     height: 100%;
     background: #202538;
     display: inline-block;
     text-align: right;
     min-width: 45px;
-    padding-inline-end: 5px;
     opacity: 0.3;
     font-family: monospace;
-  }
+`
+
+const Log = styled.div`
+  font-family: monospace;
+  user-select: text;
+  display: flex;
+  align-items: flex-start;
+  gap: 8px;
+  width: 100%;
+  min-height: 25px;
 `;
 
-const LogInnerPill = styled.div`
-    display: flex;
-    align-items: center;
+const LogInnerPill = styled.div<{ color: string }>`
+    display: inline-block;
+    vertical-align: middle;
+    width: 100px;
     padding: 0px 5px;
-    height: 90%;
+    height: 20px;
     color: black;
-    background-color: #949fffff;
+    background-color: ${(props) => props.color};
     border-radius: 5px;
     opacity: 1;
     font-family: monospace;
-    min-width: fit-content;
-    padding-inline-end: 5px;
-
+    cursor: pointer;
+    hover: {
+        border: 1px solid #949effff;
+    }
+    overflow: hidden;
+    white-space: nowrap;    
+    text-overflow: ellipsis;
 `
 
 const LogOuter = styled.div`

+ 16 - 3
dashboard/src/main/home/app-dashboard/expanded-app/logs/types.ts

@@ -48,10 +48,23 @@ export interface GenericLogFilter {
     displayName: string;
     default: GenericFilterOption;
     options: GenericFilterOption[];
-    setOption: (option: GenericFilterOption) => void;
+    setValue: (value: string) => void;
 }
 export const GenericLogFilter = {
     isDefault: (filter: GenericLogFilter, value: string) => {
         return filter.default.value === value;
-    }
-}
+    },
+
+    getDefaultOption: (filterName: LogFilterName) => {
+        switch (filterName) {
+            case 'revision':
+                return GenericFilterOption.of('All', 'all');
+            case 'output_stream':
+                return GenericFilterOption.of('stdout', 'stdout');
+            case 'pod_name':
+                return GenericFilterOption.of('All', 'all');
+            default:
+                return GenericFilterOption.of('All', 'all');
+        }
+    },
+}

+ 96 - 17
dashboard/src/main/home/app-dashboard/expanded-app/logs/utils.ts

@@ -6,7 +6,7 @@ import Anser from "anser";
 import { Context } from "shared/Context";
 import { useWebsockets, NewWebsocketOptions } from "shared/hooks/useWebsockets";
 import { ChartType } from "shared/types";
-import { AgentLog, AgentLogSchema, Direction, PorterLog, PaginationInfo } from "./types";
+import { AgentLog, AgentLogSchema, Direction, PorterLog, PaginationInfo, GenericLogFilter, LogFilterName } from "./types";
 
 const MAX_LOGS = 5000;
 const MAX_BUFFER_LOGS = 1000;
@@ -36,8 +36,7 @@ export const parseLogs = (logs: any[] = []): PorterLog[] => {
 };
 
 export const useLogs = (
-  currentPodName: string,
-  currentPodType: string,
+  selectedFilterValues: Record<LogFilterName, string>,
   namespace: string,
   searchParam: string,
   notify: (message: string) => void,
@@ -48,7 +47,7 @@ export const useLogs = (
   timeRange?: {
     startTime?: Dayjs,
     endTime?: Dayjs,
-  }
+  },
 ) => {
   const isLive = !setDate;
   const logsBufferRef = useRef<PorterLog[]>([]);
@@ -61,11 +60,9 @@ export const useLogs = (
     nextCursor: null,
   });
 
-  // if currentPodName is empty assume we are looking at all chart pod logs
-  const currentPod =
-    currentPodName == ""
-      ? currentChart?.name
-      : `${currentChart?.name}-${currentPodName}-${currentPodType}`;
+  // if currentPodName is default value we are looking at all chart pod logs
+  const currentPodSelector = selectedFilterValues.pod_name === GenericLogFilter.getDefaultOption("pod_name").value
+    ? currentChart?.name : `${currentChart?.name}-${selectedFilterValues.pod_name}`;
 
   // if we are live:
   // - start date is initially set to 2 weeks ago
@@ -95,7 +92,6 @@ export const useLogs = (
 
     setLogs((logs) => {
       let updatedLogs = _.cloneDeep(logs);
-
       /**
        * If direction = Direction.forward, we want to append the new logs
        * at the end of the current logs, else we want to append before the current logs
@@ -134,7 +130,7 @@ export const useLogs = (
         }
       }
 
-      return updatedLogs;
+      return filterLogs(updatedLogs);
     });
   };
 
@@ -167,7 +163,7 @@ export const useLogs = (
     const websocketBaseURL = `/api/projects/${currentProject.id}/clusters/${currentCluster.id}/namespaces/${namespace}/logs/loki`;
 
     const q = new URLSearchParams({
-      pod_selector: currentPod + "-.*",
+      pod_selector: currentPodSelector + "-.*",
       namespace,
       search_param: searchParam,
       revision: currentChart.version.toString(),
@@ -195,7 +191,9 @@ export const useLogs = (
             // console.log(err)
           }
         });
-        pushLogs(parseLogs(newLogs));
+        const newLogsParsed = parseLogs(newLogs);
+        const newLogsFiltered = filterLogs(newLogsParsed);
+        pushLogs(newLogsFiltered);
       },
       onclose: () => {
         console.log("Closed websocket:", websocketKey);
@@ -206,6 +204,23 @@ export const useLogs = (
     openWebsocket(websocketKey);
   };
 
+  const filterLogs = (logs: PorterLog[]) => {
+    return logs.filter(log => {
+      if (log.metadata == null) {
+        return true;
+      }
+      if (selectedFilterValues.output_stream !== GenericLogFilter.getDefaultOption("output_stream").value &&
+        log.metadata.output_stream !== selectedFilterValues.output_stream) {
+        return false;
+      }
+      if (selectedFilterValues.revision !== GenericLogFilter.getDefaultOption("revision").value &&
+        log.metadata.revision !== selectedFilterValues.revision) {
+        return false;
+      }
+      return true;
+    });
+  };
+
   const queryLogs = async (
     startDate: string,
     endDate: string,
@@ -231,7 +246,7 @@ export const useLogs = (
       end_range: endDate,
       limit,
       chart_name: "",
-      pod_selector: currentPod + "-.*",
+      pod_selector: currentPodSelector + "-.*",
       direction,
     };
 
@@ -290,7 +305,7 @@ export const useLogs = (
   };
 
   const refresh = async () => {
-    if (!currentPod) {
+    if (!currentPodSelector) {
       return;
     }
 
@@ -321,7 +336,7 @@ export const useLogs = (
 
     closeAllWebsockets();
     const suffix = Math.random().toString(36).substring(2, 15);
-    const websocketKey = `${currentPod}-${namespace}-websocket-${suffix}`;
+    const websocketKey = `${currentPodSelector}-${namespace}-websocket-${suffix}`;
 
     setLoading(false);
 
@@ -418,7 +433,7 @@ export const useLogs = (
 
   useEffect(() => {
     refresh();
-  }, [currentPod, namespace, searchParam, setDate]);
+  }, [currentPodSelector, namespace, searchParam, setDate, selectedFilterValues]);
 
   useEffect(() => {
     // if the streaming is no longer live, close all websockets
@@ -440,3 +455,67 @@ export const useLogs = (
     paginationInfo,
   };
 };
+
+export const getVersionTagColor = (version: string) => {
+  const colors = [
+    "#7B61FF",
+    "#FF7B61",
+    "#61FF7B",
+  ];
+
+  const versionInt = parseInt(version);
+  if (isNaN(versionInt)) {
+    return colors[0];
+  }
+  return colors[versionInt % colors.length];
+};
+
+export const getServiceNameFromPodNameAndAppName = (podName: string, porterAppName: string) => {
+  const prefix: string = porterAppName + "-";
+  if (!podName.startsWith(prefix)) {
+    return "";
+  }
+
+  podName = podName.replace(prefix, "");
+  const suffixes: string[] = ["-web", "-wkr", "-job"];
+  let index: number = -1;
+
+  for (const suffix of suffixes) {
+    const newIndex: number = podName.lastIndexOf(suffix);
+    if (newIndex > index) {
+      index = newIndex;
+    }
+  }
+
+  if (index !== -1) {
+    return podName.substring(0, index);
+  }
+
+  return "";
+}
+
+export const getPodSelectorFromPodNameAndAppName = (podName: string, porterAppName: string) => {
+  const prefix: string = porterAppName + "-";
+  if (!podName.startsWith(prefix)) {
+    return "";
+  }
+
+  podName = podName.replace(prefix, "");
+  const suffixes: string[] = ["-web", "-wkr", "-job"];
+  let index: number = -1;
+  let type = ""
+
+  for (const suffix of suffixes) {
+    const newIndex: number = podName.lastIndexOf(suffix);
+    if (newIndex > index) {
+      index = newIndex;
+      type = suffix;
+    }
+  }
+
+  if (index !== -1) {
+    return podName.substring(0, index) + type;
+  }
+
+  return "";
+}