Explorar o código

turn logs into grid, services are expanded when created (#3318)

Feroze Mohideen %!s(int64=2) %!d(string=hai) anos
pai
achega
546b301ecc

+ 1 - 1
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/events/cards/PreDeployEventCard.tsx

@@ -30,7 +30,7 @@ const PreDeployEventCard: React.FC<Props> = ({ event, appData }) => {
       case "FAILED":
         return <Text color="#FF6060">Pre-deploy failed</Text>;
       default:
-        return <Text color="#aaaabb66">Pre-deploy in progress...</Text>;
+        return <Text color="helper">Pre-deploy in progress...</Text>;
     }
   };
 

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

@@ -265,19 +265,8 @@ const LogSection: React.FC<Props> = ({
         }
         <LogsSectionWrapper>
           <StyledLogsSection>
-            {isLoading || (logs.length == 0 && selectedDate == null) ? (
-              <Loading message="Waiting for logs..." />
-            ) : logs.length == 0 ? (
-              <>
-                <Message>
-                  No logs found.
-                  <Highlight onClick={refresh}>
-                    <i className="material-icons">autorenew</i>
-                    Refresh
-                  </Highlight>
-                </Message>
-              </>
-            ) : (
+            {isLoading && <Loading message="Waiting for logs..." />}
+            {!isLoading && logs.length !== 0 && (
               <>
                 <LoadMoreButton
                   active={
@@ -302,6 +291,18 @@ const LogSection: React.FC<Props> = ({
                 </LoadMoreButton>
               </>
             )}
+            {!isLoading && logs.length === 0 && selectedDate != null && (
+              <Message>
+                No logs found for this time range.
+                <Highlight onClick={() => setSelectedDate(undefined)}>
+                  <i className="material-icons">autorenew</i>
+                  Reset
+                </Highlight>
+              </Message>
+            )}
+            {!isLoading && logs.length === 0 && selectedDate == null && (
+              <Loading message="Waiting for logs..." />
+            )}
             <div ref={scrollToBottomRef} />
           </StyledLogsSection>
           <NotificationWrapper

+ 68 - 62
dashboard/src/main/home/app-dashboard/expanded-app/logs/StyledLogs.tsx

@@ -27,26 +27,30 @@ const StyledLogs: React.FC<Props> = ({
                     return null;
                 }
                 return (
-                    <LogInnerPill
-                        color={getVersionTagColor(log.metadata.revision)}
-                        key={index}
-                        onClick={() => filter.setValue(log.metadata.revision)}
-                    >
-                        {`Version: ${log.metadata.revision}`}
-                    </LogInnerPill>
+                    <StyledLogsTableData width={"100px"}>
+                        <LogInnerPill
+                            color={getVersionTagColor(log.metadata.revision)}
+                            key={index}
+                            onClick={() => filter.setValue(log.metadata.revision)}
+                        >
+                            {`Version: ${log.metadata.revision}`}
+                        </LogInnerPill>
+                    </StyledLogsTableData>
                 )
             case "pod_name":
                 if (log.metadata.pod_name == null || log.metadata.pod_name === "") {
                     return null;
                 }
                 return (
-                    <LogInnerPill
-                        color={"white"}
-                        key={index}
-                        onClick={() => filter.setValue(getPodSelectorFromPodNameAndAppName(log.metadata.pod_name, appName))}
-                    >
-                        {getServiceNameFromPodNameAndAppName(log.metadata.pod_name, appName)}
-                    </LogInnerPill>
+                    <StyledLogsTableData width={"100px"}>
+                        <LogInnerPill
+                            color={"white"}
+                            key={index}
+                            onClick={() => filter.setValue(getPodSelectorFromPodNameAndAppName(log.metadata.pod_name, appName))}
+                        >
+                            {getServiceNameFromPodNameAndAppName(log.metadata.pod_name, appName)}
+                        </LogInnerPill>
+                    </StyledLogsTableData>
                 )
             default:
                 return null;
@@ -54,73 +58,75 @@ const StyledLogs: React.FC<Props> = ({
     }
 
     return (
-        <StyledLogsContainer>
-            {logs.map((log, i) => {
-                return (
-                    <Log key={[log.lineNumber, i].join(".")}>
-                        <LogLabelsContainer>
-                            <LineTimestamp className="line-timestamp">
-                                {log.timestamp
-                                    ? dayjs(log.timestamp).format("MM/DD HH:mm:ss")
-                                    : "-"}
-                            </LineTimestamp>
+        <StyledLogsTable>
+            <StyledLogsTableBody>
+                {logs.map((log, i) => {
+                    return (
+                        <StyledLogsTableRow key={[log.lineNumber, i].join(".")}>
+                            <StyledLogsTableData width={"100px"}>
+                                <LineTimestamp className="line-timestamp">
+                                    {log.timestamp
+                                        ? dayjs(log.timestamp).format("MM/DD HH:mm:ss")
+                                        : "-"}
+                                </LineTimestamp>
+                            </StyledLogsTableData>
                             {filters.map((filter, j) => {
                                 return renderFilterTagForLog(filter, log, j)
                             })}
-                        </LogLabelsContainer>
-                        <LogOuter key={[log.lineNumber, i].join(".")}>
-                            {log.line?.map((ansi, j) => {
-                                if (ansi.clearLine) {
-                                    return null;
-                                }
+                            <StyledLogsTableData>
+                                <LogOuter key={[log.lineNumber, i].join(".")}>
+                                    {log.line?.map((ansi, j) => {
+                                        if (ansi.clearLine) {
+                                            return null;
+                                        }
 
-                                return (
-                                    <LogInnerSpan
-                                        key={[log.lineNumber, i, j].join(".")}
-                                        ansi={ansi}
-                                    >
-                                        {ansi.content.replace(/ /g, "\u00a0")}
-                                    </LogInnerSpan>
-                                );
-                            })}
-                        </LogOuter>
-                    </Log>
-                );
-            })}
-        </StyledLogsContainer>
+                                        return (
+                                            <LogInnerSpan
+                                                key={[log.lineNumber, i, j].join(".")}
+                                                ansi={ansi}
+                                            >
+                                                {ansi.content.replace(/ /g, "\u00a0")}
+                                            </LogInnerSpan>
+                                        );
+                                    })}
+                                </LogOuter>
+                            </StyledLogsTableData>
+                        </StyledLogsTableRow>
+                    )
+                })}
+            </StyledLogsTableBody>
+
+        </StyledLogsTable>
     );
 };
 
 export default StyledLogs;
 
-const StyledLogsContainer = styled.div`
+const StyledLogsTable = styled.table`
+    border-collapse: collapse;
+`;
+
+const StyledLogsTableBody = styled.tbody`
 `;
 
-const LogLabelsContainer = styled.div`
-    display: flex;
-    flex-direction: row;
-    align-items: center;
-    gap: 10px;
+const StyledLogsTableRow = styled.tr`
+    
 `;
 
-const LineTimestamp = styled.span`
+const StyledLogsTableData = styled.td<{ width?: string }>`
+    padding: 2px;
+    vertical-align: top;
+    ${(props) => props.width && `width: ${props.width};`}
+`;
+
+const LineTimestamp = styled.div`
     height: 100%;
     color: #949effff;
     opacity: 0.5;
     font-family: monospace;
-    min-width: fit-content;
+    white-space: nowrap;
 `
 
-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<{ color: string }>`
     display: inline-block;
     vertical-align: middle;

+ 0 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -635,7 +635,6 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                     setFormState({ ...formState, serviceList: [...nonRelease, ...release] });
                   }}
                   services={formState.serviceList.filter(Service.isRelease)}
-                  defaultExpanded={true}
                   limitOne={true}
                   addNewText={"Add a new pre-deploy job"}
                   prePopulateService={Service.default("pre-deploy", "release", porterJsonWithPath?.porterJson)}

+ 10 - 12
dashboard/src/main/home/app-dashboard/new-app-flow/ServiceContainer.tsx

@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useMemo } from "react";
+import React from "react";
 import AnimateHeight, { Height } from "react-animate-height";
 import styled from "styled-components";
 import _ from "lodash";
@@ -20,7 +20,6 @@ interface ServiceProps {
   chart?: any;
   editService: (service: Service) => void;
   deleteService: () => void;
-  defaultExpanded: boolean;
   setExpandedJob: (x: string) => void;
 }
 
@@ -29,12 +28,8 @@ const ServiceContainer: React.FC<ServiceProps> = ({
   chart,
   deleteService,
   editService,
-  defaultExpanded,
   setExpandedJob,
 }) => {
-  const [showExpanded, setShowExpanded] = React.useState<boolean>(
-    defaultExpanded
-  );
   const [height, setHeight] = React.useState<Height>("auto");
 
   // TODO: calculate heights instead of hardcoding them
@@ -98,10 +93,10 @@ const ServiceContainer: React.FC<ServiceProps> = ({
   return (
     <>
       <ServiceHeader
-        showExpanded={showExpanded}
-        onClick={() => setShowExpanded(!showExpanded)}
+        showExpanded={service.expanded}
+        onClick={() => editService({ ...service, expanded: !service.expanded })}
         chart={chart}
-        bordersRounded={!getHasBuiltImage() && !showExpanded}
+        bordersRounded={!getHasBuiltImage() && !service.expanded}
       >
         <ServiceTitle>
           <ActionButton>
@@ -111,14 +106,17 @@ const ServiceContainer: React.FC<ServiceProps> = ({
           {service.name.trim().length > 0 ? service.name : "New Service"}
         </ServiceTitle>
         {service.canDelete && (
-          <ActionButton onClick={deleteService}>
+          <ActionButton onClick={(e) => {
+            e.stopPropagation();
+            deleteService();
+          }}>
             <span className="material-icons">delete</span>
           </ActionButton>
         )}
       </ServiceHeader>
-      <AnimateHeight height={showExpanded ? height : 0}>
+      <AnimateHeight height={service.expanded ? height : 0}>
         <StyledSourceBox
-          showExpanded={showExpanded}
+          showExpanded={service.expanded}
           chart={chart}
           hasFooter={chart && service && getHasBuiltImage()}
         >

+ 0 - 2
dashboard/src/main/home/app-dashboard/new-app-flow/Services.tsx

@@ -30,7 +30,6 @@ const Services: React.FC<ServicesProps> = ({
   setServices,
   addNewText,
   chart,
-  defaultExpanded = false,
   limitOne = false,
   setExpandedJob,
   prePopulateService,
@@ -109,7 +108,6 @@ const Services: React.FC<ServicesProps> = ({
                   const newServices = services.filter((_, i) => i !== index);
                   setServices(newServices);
                 }}
-                defaultExpanded={defaultExpanded}
               />
             );
           })}

+ 9 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -116,6 +116,7 @@ type SharedServiceParams = {
     startCommand: ServiceString;
     type: ServiceType;
     canDelete: boolean;
+    expanded: boolean;
     cloudsql: CloudSql;
 }
 
@@ -128,6 +129,7 @@ export type WebService = SharedServiceParams & Omit<WorkerService, 'type'> & {
 const WebService = {
     default: (name: string, porterJson?: PorterJson): WebService => ({
         name,
+        expanded: true,
         cpu: ServiceField.string('100', porterJson?.apps?.[name]?.config?.resources?.requests?.cpu ? porterJson?.apps?.[name]?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
         ram: ServiceField.string('256', porterJson?.apps?.[name]?.config?.resources?.requests?.memory ? porterJson?.apps?.[name]?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
         startCommand: ServiceField.string('', porterJson?.apps?.[name]?.run),
@@ -237,6 +239,7 @@ const WebService = {
     deserialize: (name: string, values: any, porterJson?: PorterJson): WebService => {
         return {
             name,
+            expanded: false,
             cpu: ServiceField.string(values.resources?.requests?.cpu?.replace('m', ''), porterJson?.apps?.[name]?.config?.resources?.requests?.cpu ? porterJson?.apps?.[name]?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
             ram: ServiceField.string(values.resources?.requests?.memory?.replace('Mi', '') ?? '', porterJson?.apps?.[name]?.config?.resources?.requests?.memory ? porterJson?.apps?.[name]?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
             startCommand: ServiceField.string(values.container?.command ?? '', porterJson?.apps?.[name]?.run),
@@ -296,6 +299,7 @@ export type WorkerService = SharedServiceParams & {
 const WorkerService = {
     default: (name: string, porterJson?: PorterJson): WorkerService => ({
         name,
+        expanded: true,
         cpu: ServiceField.string('100', porterJson?.apps?.[name]?.config?.resources?.requests?.cpu ? porterJson?.apps?.[name]?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
         ram: ServiceField.string('256', porterJson?.apps?.[name]?.config?.resources?.requests?.memory ? porterJson?.apps?.[name]?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
         startCommand: ServiceField.string('', porterJson?.apps?.[name]?.run),
@@ -346,6 +350,7 @@ const WorkerService = {
     deserialize: (name: string, values: any, porterJson?: PorterJson): WorkerService => {
         return {
             name,
+            expanded: false,
             cpu: ServiceField.string(values.resources?.requests?.cpu?.replace('m', ''), porterJson?.apps?.[name]?.config?.resources?.requests?.cpu ? porterJson?.apps?.[name]?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
             ram: ServiceField.string(values.resources?.requests?.memory?.replace('Mi', '') ?? '', porterJson?.apps?.[name]?.config?.resources?.requests?.memory ? porterJson?.apps?.[name]?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
             startCommand: ServiceField.string(values.container?.command ?? '', porterJson?.apps?.[name]?.run),
@@ -377,6 +382,7 @@ export type JobService = SharedServiceParams & {
 const JobService = {
     default: (name: string, porterJson?: PorterJson): JobService => ({
         name,
+        expanded: true,
         cpu: ServiceField.string('100', porterJson?.apps?.[name]?.config?.resources?.requests?.cpu ? porterJson?.apps?.[name]?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
         ram: ServiceField.string('256', porterJson?.apps?.[name]?.config?.resources?.requests?.memory ? porterJson?.apps?.[name]?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
         startCommand: ServiceField.string('', porterJson?.apps?.[name]?.run),
@@ -419,6 +425,7 @@ const JobService = {
     deserialize: (name: string, values: any, porterJson?: PorterJson): JobService => {
         return {
             name,
+            expanded: false,
             cpu: ServiceField.string(values.resources?.requests?.cpu?.replace('m', ''), porterJson?.apps?.[name]?.config?.resources?.requests?.cpu ? porterJson?.apps?.[name]?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
             ram: ServiceField.string(values.resources?.requests?.memory?.replace('Mi', '') ?? '', porterJson?.apps?.[name]?.config?.resources?.requests?.memory ? porterJson?.apps?.[name]?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
             startCommand: ServiceField.string(values.container?.command ?? '', porterJson?.apps?.[name]?.run),
@@ -442,6 +449,7 @@ export type ReleaseService = SharedServiceParams & {
 const ReleaseService = {
     default: (name: string, porterJson?: PorterJson): ReleaseService => ({
         name,
+        expanded: true,
         cpu: ServiceField.string('100', porterJson?.release?.config?.resources?.requests?.cpu ? porterJson?.release?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
         ram: ServiceField.string('256', porterJson?.release?.config?.resources?.requests?.memory ? porterJson?.release?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
         startCommand: ServiceField.string('', porterJson?.release?.run),
@@ -479,6 +487,7 @@ const ReleaseService = {
     deserialize: (name: string, values: any, porterJson?: PorterJson): ReleaseService => {
         return {
             name,
+            expanded: false,
             cpu: ServiceField.string(values?.resources?.requests?.cpu?.replace('m', ''), porterJson?.release?.config?.resources?.requests?.cpu ? porterJson?.release?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
             ram: ServiceField.string(values?.resources?.requests?.memory?.replace('Mi', '') ?? '', porterJson?.release?.config?.resources?.requests?.memory ? porterJson?.release?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
             startCommand: ServiceField.string(values?.container?.command ?? '', porterJson?.release?.run),