mirror of
https://github.com/harness/drone.git
synced 2025-05-04 23:59:05 +08:00
pipeline console WIP
This commit is contained in:
parent
6ca2a6924d
commit
15a4fe04bd
32
web/src/components/Console/Console.module.scss
Normal file
32
web/src/components/Console/Console.module.scss
Normal file
@ -0,0 +1,32 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: white;
|
||||
font-family: Inconsolata, monospace;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--black);
|
||||
height: var(--log-content-header-height);
|
||||
|
||||
.headerLayout {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
border-bottom: 1px solid var(--grey-800);
|
||||
padding: var(--spacing-medium) 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.steps {
|
||||
padding: var(--spacing-medium) !important;
|
||||
}
|
10
web/src/components/Console/Console.module.scss.d.ts
vendored
Normal file
10
web/src/components/Console/Console.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly container: string
|
||||
readonly log: string
|
||||
readonly header: string
|
||||
readonly headerLayout: string
|
||||
readonly steps: string
|
||||
}
|
||||
export default styles
|
48
web/src/components/Console/Console.tsx
Normal file
48
web/src/components/Console/Console.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { FC } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Container, Layout, Text } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import type { CODEProps } from 'RouteDefinitions'
|
||||
import type { TypesStage } from 'services/code'
|
||||
import ConsoleStep from 'components/ConsoleStep/ConsoleStep'
|
||||
import css from './Console.module.scss'
|
||||
|
||||
interface ConsoleProps {
|
||||
stage: TypesStage | undefined
|
||||
}
|
||||
|
||||
const Console: FC<ConsoleProps> = ({ stage }) => {
|
||||
const space = useGetSpaceParam()
|
||||
const { pipeline, execution: executionNum } = useParams<CODEProps>()
|
||||
|
||||
return (
|
||||
<div className={css.container}>
|
||||
<Container className={css.header}>
|
||||
<Layout.Horizontal className={css.headerLayout} spacing="small">
|
||||
<Text font={{ variation: FontVariation.H4 }} color={Color.WHITE} padding={{ left: 'large', right: 'large' }}>
|
||||
{stage?.name}
|
||||
</Text>
|
||||
<Text font={{ variation: FontVariation.BODY }} color={Color.GREY_500}>
|
||||
{/* this needs fixed */}
|
||||
Success in 5 mins
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<Layout.Vertical className={css.steps} spacing="small">
|
||||
{stage?.steps?.map((step, index) => (
|
||||
<ConsoleStep
|
||||
key={index}
|
||||
step={step}
|
||||
executionNumber={Number(executionNum)}
|
||||
pipelineName={pipeline}
|
||||
spaceName={space}
|
||||
stageNumber={stage.number}
|
||||
/>
|
||||
))}
|
||||
</Layout.Vertical>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Console
|
14
web/src/components/ConsoleLogs/ConsoleLogs.module.scss
Normal file
14
web/src/components/ConsoleLogs/ConsoleLogs.module.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.logLayout {
|
||||
margin-left: 2.3rem !important;
|
||||
}
|
||||
|
||||
.lineNumber {
|
||||
width: 1.5rem;
|
||||
color: #999;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: white !important;
|
||||
margin-bottom: 1rem;
|
||||
}
|
8
web/src/components/ConsoleLogs/ConsoleLogs.module.scss.d.ts
vendored
Normal file
8
web/src/components/ConsoleLogs/ConsoleLogs.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly logLayout: string
|
||||
readonly lineNumber: string
|
||||
readonly log: string
|
||||
}
|
||||
export default styles
|
36
web/src/components/ConsoleLogs/ConsoleLogs.tsx
Normal file
36
web/src/components/ConsoleLogs/ConsoleLogs.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Layout, Text } from '@harnessio/uicore'
|
||||
import React, { FC } from 'react'
|
||||
import css from './ConsoleLogs.module.scss'
|
||||
|
||||
// currently a string - should be an array of strings in future
|
||||
interface ConsoleLogsProps {
|
||||
logs: string
|
||||
}
|
||||
|
||||
interface log {
|
||||
pos: number
|
||||
out: string
|
||||
time: number
|
||||
}
|
||||
|
||||
const convertStringToLogArray = (logs: string): log[] => {
|
||||
const logStrings = logs.split('\n').map(log => {
|
||||
return JSON.parse(log)
|
||||
})
|
||||
|
||||
return logStrings
|
||||
}
|
||||
|
||||
const ConsoleLogs: FC<ConsoleLogsProps> = ({ logs }) => {
|
||||
const logArray = convertStringToLogArray(logs)
|
||||
return logArray.map((log, index) => {
|
||||
return (
|
||||
<Layout.Horizontal key={index} spacing={'medium'} className={css.logLayout}>
|
||||
<Text className={css.lineNumber}>{log.pos}</Text>
|
||||
<Text className={css.log}>{log.out}</Text>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default ConsoleLogs
|
5
web/src/components/ConsoleStep/ConsoleStep.module.scss
Normal file
5
web/src/components/ConsoleStep/ConsoleStep.module.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.stepLayout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
6
web/src/components/ConsoleStep/ConsoleStep.module.scss.d.ts
vendored
Normal file
6
web/src/components/ConsoleStep/ConsoleStep.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly stepLayout: string
|
||||
}
|
||||
export default styles
|
64
web/src/components/ConsoleStep/ConsoleStep.tsx
Normal file
64
web/src/components/ConsoleStep/ConsoleStep.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { FlexExpander, Layout } from '@harnessio/uicore'
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { useGet } from 'restful-react'
|
||||
import { Text } from '@harnessio/uicore'
|
||||
import type { TypesStep } from 'services/code'
|
||||
import { timeDistance } from 'utils/Utils'
|
||||
import ConsoleLogs from 'components/ConsoleLogs/ConsoleLogs'
|
||||
import css from './ConsoleStep.module.scss'
|
||||
|
||||
interface ConsoleStepProps {
|
||||
step: TypesStep | undefined
|
||||
stageNumber: number | undefined
|
||||
spaceName: string
|
||||
pipelineName: string | undefined
|
||||
executionNumber: number
|
||||
}
|
||||
|
||||
const ConsoleStep: FC<ConsoleStepProps> = ({ step, stageNumber, spaceName, pipelineName, executionNumber }) => {
|
||||
const [isOpened, setIsOpened] = React.useState(false)
|
||||
|
||||
const { data, error, loading, refetch } = useGet<string>({
|
||||
path: `/api/v1/pipelines/${spaceName}/${pipelineName}/+/executions/${executionNumber}/logs/${String(
|
||||
stageNumber
|
||||
)}/${String(step?.number)}`,
|
||||
lazy: true
|
||||
})
|
||||
|
||||
// this refetches any open steps when the stage number changes - really it shouldnt refetch until reopened...
|
||||
useEffect(() => {
|
||||
setIsOpened(false)
|
||||
refetch()
|
||||
}, [stageNumber, refetch])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout.Horizontal
|
||||
className={css.stepLayout}
|
||||
spacing="medium"
|
||||
onClick={() => {
|
||||
setIsOpened(!isOpened)
|
||||
if (!data && !loading) refetch()
|
||||
}}>
|
||||
<Icon name={isOpened ? 'chevron-down' : 'chevron-right'} />
|
||||
<Icon name={step?.status === 'Success' ? 'success-tick' : 'circle'} />
|
||||
<Text>{step?.name}</Text>
|
||||
<FlexExpander />
|
||||
{step?.started && step?.stopped && <div>{timeDistance(step?.stopped, step?.started)}</div>}
|
||||
</Layout.Horizontal>
|
||||
|
||||
{isOpened ? (
|
||||
loading ? (
|
||||
<div>Loading...</div>
|
||||
) : error ? (
|
||||
<div>Error: {error}</div>
|
||||
) : data ? (
|
||||
<ConsoleLogs logs={data} />
|
||||
) : null
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConsoleStep
|
@ -0,0 +1,39 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menu {
|
||||
--stage-title-height: 54px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.menuItem {
|
||||
margin: 0.5rem 0 0.5rem 1rem !important;
|
||||
cursor: pointer;
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: var(--stage-title-height);
|
||||
padding: 0 var(--spacing-medium) 0 var(--spacing-medium);
|
||||
border-radius: 10px 0 0 10px !important;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--primary-1);
|
||||
|
||||
.uid {
|
||||
color: var(--primary-7) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.uid {
|
||||
color: var(--grey-700) !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
web/src/components/ExecutionStageList/ExecutionStageList.module.scss.d.ts
vendored
Normal file
11
web/src/components/ExecutionStageList/ExecutionStageList.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly container: string
|
||||
readonly menu: string
|
||||
readonly menuItem: string
|
||||
readonly layout: string
|
||||
readonly selected: string
|
||||
readonly uid: string
|
||||
}
|
||||
export default styles
|
56
web/src/components/ExecutionStageList/ExecutionStageList.tsx
Normal file
56
web/src/components/ExecutionStageList/ExecutionStageList.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Container, Layout, Text } from '@harnessio/uicore'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import cx from 'classnames'
|
||||
import type { TypesStage } from 'services/code'
|
||||
import css from './ExecutionStageList.module.scss'
|
||||
|
||||
interface ExecutionStageListProps {
|
||||
stages: TypesStage[]
|
||||
selectedStage: number | null
|
||||
setSelectedStage: (selectedStep: number | null) => void
|
||||
}
|
||||
|
||||
interface ExecutionStageProps {
|
||||
stage: TypesStage
|
||||
isSelected?: boolean
|
||||
selectedStage: number | null
|
||||
setSelectedStage: (selectedStage: number | null) => void
|
||||
}
|
||||
|
||||
const ExecutionStage: FC<ExecutionStageProps> = ({ stage, isSelected = false, setSelectedStage = () => {} }) => {
|
||||
return (
|
||||
<Container
|
||||
className={css.menuItem}
|
||||
onClick={() => {
|
||||
setSelectedStage(stage.number || null)
|
||||
}}>
|
||||
<Layout.Horizontal spacing="small" className={cx(css.layout, { [css.selected]: isSelected })}>
|
||||
<Icon name="success-tick" size={16} />
|
||||
<Text className={css.uid} lineClamp={1}>
|
||||
{stage.name}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const ExecutionStageList: FC<ExecutionStageListProps> = ({ stages, setSelectedStage, selectedStage }) => {
|
||||
return (
|
||||
<Container className={css.menu}>
|
||||
{stages.map((stage, index) => {
|
||||
return (
|
||||
<ExecutionStage
|
||||
key={index}
|
||||
stage={stage}
|
||||
isSelected={selectedStage === stage.number}
|
||||
selectedStage={selectedStage}
|
||||
setSelectedStage={setSelectedStage}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExecutionStageList
|
@ -1,4 +1,59 @@
|
||||
.main {
|
||||
min-height: var(--page-height);
|
||||
background-color: var(--primary-bg) !important;
|
||||
|
||||
:global {
|
||||
.Resizer {
|
||||
background-color: var(--grey-300);
|
||||
opacity: 0.2;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.Resizer:hover {
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
.Resizer.horizontal {
|
||||
margin: -5px 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.Resizer.horizontal:hover {
|
||||
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Resizer.vertical {
|
||||
width: 11px;
|
||||
margin: 0 -5px;
|
||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.Resizer.vertical:hover {
|
||||
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Resizer.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.Resizer.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
height: calc(100vh - var(--page-header-height));
|
||||
}
|
||||
|
||||
.withError {
|
||||
display: grid;
|
||||
}
|
||||
|
@ -2,5 +2,7 @@
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly main: string
|
||||
readonly container: string
|
||||
readonly withError: string
|
||||
}
|
||||
export default styles
|
||||
|
@ -1,29 +1,59 @@
|
||||
import React from 'react'
|
||||
import { Container, PageHeader } from '@harnessio/uicore'
|
||||
import { Container, PageHeader, PageBody } from '@harnessio/uicore'
|
||||
import React, { useState } from 'react'
|
||||
import cx from 'classnames'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGet } from 'restful-react'
|
||||
import SplitPane from 'react-split-pane'
|
||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||
import type { CODEProps } from 'RouteDefinitions'
|
||||
import type { TypesExecution } from 'services/code'
|
||||
import ExecutionStageList from 'components/ExecutionStageList/ExecutionStageList'
|
||||
import Console from 'components/Console/Console'
|
||||
import { getErrorMessage, voidFn } from 'utils/Utils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||
import noExecutionImage from '../RepositoriesListing/no-repo.svg'
|
||||
import css from './Execution.module.scss'
|
||||
|
||||
const Execution = () => {
|
||||
const space = useGetSpaceParam()
|
||||
const { pipeline, execution: executionNum } = useParams<CODEProps>()
|
||||
const { getString } = useStrings()
|
||||
|
||||
const {
|
||||
data: execution
|
||||
// error,
|
||||
// loading,
|
||||
// refetch
|
||||
// response
|
||||
data: execution,
|
||||
error,
|
||||
loading,
|
||||
refetch
|
||||
} = useGet<TypesExecution>({
|
||||
path: `/api/v1/pipelines/${space}/${pipeline}/+/executions/${executionNum}`
|
||||
})
|
||||
|
||||
const [selectedStage, setSelectedStage] = useState<number | null>(null)
|
||||
|
||||
return (
|
||||
<Container className={css.main}>
|
||||
<PageHeader title={`EXECUTION STATUS = ${execution?.status}`} />
|
||||
<PageHeader title={execution?.title} />
|
||||
<PageBody
|
||||
className={cx({ [css.withError]: !!error })}
|
||||
error={error ? getErrorMessage(error) : null}
|
||||
retryOnError={voidFn(refetch)}
|
||||
noData={{
|
||||
when: () => !execution,
|
||||
image: noExecutionImage,
|
||||
message: getString('executions.noData')
|
||||
// button: NewExecutionButton
|
||||
}}>
|
||||
<LoadingSpinner visible={loading} />
|
||||
<SplitPane split="vertical" size={300} minSize={200} maxSize={400}>
|
||||
<ExecutionStageList
|
||||
stages={execution?.stages || []}
|
||||
setSelectedStage={setSelectedStage}
|
||||
selectedStage={selectedStage}
|
||||
/>
|
||||
{selectedStage && <Console stage={execution?.stages?.[selectedStage - 1]} />}
|
||||
</SplitPane>
|
||||
</PageBody>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ const ExecutionList = () => {
|
||||
const columns: Column<TypesExecution>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: getString('repos.name'),
|
||||
Header: getString('executions.name'),
|
||||
width: 'calc(100% - 180px)',
|
||||
Cell: ({ row }: CellProps<TypesExecution>) => {
|
||||
const record = row.original
|
||||
@ -127,7 +127,7 @@ const ExecutionList = () => {
|
||||
routes.toCODEExecution({
|
||||
space,
|
||||
pipeline: pipeline as string,
|
||||
execution: String(executionInfo.id)
|
||||
execution: String(executionInfo.number)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user