Skip to content

Commit 23921d6

Browse files
Merge branch 'main' into use_v2_true_as_default
2 parents 7e792b2 + 4580258 commit 23921d6

File tree

12 files changed

+981
-42
lines changed

12 files changed

+981
-42
lines changed

src/assets/icons/ic-lines.svg

Lines changed: 3 additions & 0 deletions
Loading

src/components/common/DatePickers/Calender.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const selectedSpanStyles = {
4040
},
4141
}
4242

43-
const customDayStyles = {
43+
export const customDayStyles = {
4444
selectedStartStyles: selectedStyles,
4545
selectedEndStyles: selectedStyles,
4646
hoveredSpanStyles: hoveredSpanStyles,

src/components/v2/appDetails/k8Resource/nodeDetail/NodeDetailTabs/CustomLogsModal/CustomLogsModal.tsx

Lines changed: 376 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
.custom-logs-modal {
2+
margin: auto;
3+
display: flex;
4+
flex-direction: column;
5+
margin-top: 40px;
6+
background-color: white;
7+
}
8+
9+
.custom-logs-radio-group {
10+
display: flex;
11+
flex-direction: column;
12+
align-self: stretch;
13+
border: none;
14+
border-radius: 0;
15+
width: 180px;
16+
padding: 16px 12px;
17+
border-right: 1px solid var(--N100);
18+
19+
.form__radio-item {
20+
border: none;
21+
flex-grow: 0;
22+
}
23+
24+
.form__radio-item-content {
25+
display: flex;
26+
align-items: center;
27+
padding: 6px 8px;
28+
.radio__title{
29+
line-height: 20px;
30+
}
31+
}
32+
33+
.form__radio-item-content:hover {
34+
background-color: var(--N50);
35+
}
36+
}
37+
38+
.option-input-container {
39+
align-self: flex-start;
40+
padding: 16px 18px;
41+
flex-grow: 1;
42+
}
43+
44+
.input-focus-none {
45+
width: 175px;
46+
padding: 8px;
47+
border-radius: 4px 0 0 4px;
48+
border-width: 1px 0 1px 1px;
49+
border-style: solid;
50+
border-color: var(--N200);
51+
color: var(--N900);
52+
font-size: 13px;
53+
line-height: 20px;
54+
55+
&:focus,
56+
&:hover {
57+
outline: none;
58+
}
59+
}
60+
61+
.logs-date-picker {
62+
display: flex;
63+
padding: 8px;
64+
justify-content: flex-start;
65+
width: 160px;
66+
margin-right: 8px;
67+
border-radius: 4px;
68+
height: 38px;
69+
border: 1px solid var(--N200);
70+
background-color: var(--N50);
71+
72+
}
73+
74+
.SingleDatePickerInput {
75+
display: flex;
76+
width: 169px;
77+
height: 38px;
78+
padding: 8px 11px 4px;
79+
border: 1px solid var(--N200);
80+
background: var(--N50);
81+
margin-right: 8px;
82+
83+
}
84+
85+
.SingleDatePickerInput_calendarIcon {
86+
padding: 0;
87+
margin: 0;
88+
margin-right: 8px
89+
}
90+
91+
.SingleDatePickerInput__withBorder .DateInput {
92+
height: max-content;
93+
background: var(--N50)
94+
}
95+
96+
.DateInput_input {
97+
font-size: 13px;
98+
color: var(--N900);
99+
padding: 0px;
100+
background: var(--N50);
101+
102+
&.DateInput_input__focused {
103+
border: none
104+
}
105+
}
106+
107+
.DateInput_fang {
108+
display: none;
109+
}

src/components/v2/appDetails/k8Resource/nodeDetail/NodeDetailTabs/Logs.component.tsx

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ import { ReactComponent as PlayButton } from '../../../../../../assets/icons/ic-
44
import { ReactComponent as StopButton } from '../../../../../../assets/icons/ic-stop-filled.svg'
55
import { ReactComponent as Search } from '../../../../../../assets/icons/ic-search.svg'
66
import { ReactComponent as Abort } from '../../../../assets/icons/ic-abort.svg'
7+
import { ReactComponent as LinesIcon } from '../../../../../../assets/icons/ic-lines.svg'
8+
import { ReactComponent as Download } from '../../../../../../assets/icons/ic-arrow-line-down.svg'
79
import { useParams, useRouteMatch, useLocation } from 'react-router'
810
import { NodeDetailTab } from '../nodeDetail.type'
9-
import { getLogsURL } from '../nodeDetail.api'
11+
import { downloadLogs, getLogsURL } from '../nodeDetail.api'
1012
import IndexStore from '../../../index.store'
1113
import WebWorker from '../../../../../app/WebWorker'
1214
import sseWorker from '../../../../../app/grepSSEworker'
13-
import { Checkbox, CHECKBOX_VALUE, Host } from '@devtron-labs/devtron-fe-common-lib'
15+
import { Checkbox, CHECKBOX_VALUE, Host, Progressing } from '@devtron-labs/devtron-fe-common-lib'
1416
import { Subject } from '../../../../../../util/Subject'
1517
import LogViewerComponent from './LogViewer.component'
1618
import { useKeyDown } from '../../../../../common'
1719
import { toast } from 'react-toastify'
1820
import Select from 'react-select'
19-
import { multiSelectStyles } from '../../../../common/ReactSelectCustomization'
21+
import { multiSelectStyles, podsDropdownStyles } from '../../../../common/ReactSelectCustomization'
2022
import { LogsComponentProps, Options } from '../../../appDetails.type'
2123
import { ReactComponent as Question } from '../../../../assets/icons/ic-question.svg'
2224
import { ReactComponent as CloseImage } from '../../../../assets/icons/ic-cancelled.svg'
@@ -30,10 +32,15 @@ import {
3032
getGroupedContainerOptions,
3133
getInitialPodContainerSelection,
3234
getPodContainerOptions,
35+
getPodLogsOptions,
3336
getSelectedPodList,
3437
} from '../nodeDetail.util'
3538
import './nodeDetailTab.scss'
3639
import ReactGA from 'react-ga4'
40+
import { CUSTOM_LOGS_FILTER } from '../../../../../../config'
41+
import { SelectedCustomLogFilterType } from './node.type'
42+
import CustomLogsModal from './CustomLogsModal/CustomLogsModal'
43+
3744

3845
const subject: Subject<string> = new Subject()
3946
const commandLineParser = require('command-line-parser')
@@ -46,6 +53,15 @@ function LogsComponent({
4653
isResourceBrowserView,
4754
selectedResource,
4855
}: LogsComponentProps) {
56+
const [logsShownOption, setLogsShownOption] = useState({
57+
prev: getPodLogsOptions()[5],
58+
current: getPodLogsOptions()[5],
59+
})
60+
const [selectedCustomLogFilter, setSelectedCustomLogFilter] = useState<SelectedCustomLogFilterType>({
61+
option: 'duration',
62+
value: '',
63+
unit: 'minutes',
64+
})
4965
const location = useLocation()
5066
const { url } = useRouteMatch()
5167
const params = useParams<{
@@ -72,6 +88,9 @@ function LogsComponent({
7288
)
7389
const [prevContainer, setPrevContainer] = useState(false)
7490
const [showNoPrevContainer, setNoPrevContainer] = useState('')
91+
const [newFilteredLogs, setNewFilteredLogs] = useState<boolean>(false)
92+
const [showCustomOptionsModal, setShowCustomOptionsMoadal] = useState(false)
93+
const [downloadInProgress, setDownloadInProgress] = useState(false)
7594

7695
const getPrevContainerLogs = () => {
7796
setPrevContainer(!prevContainer)
@@ -203,6 +222,49 @@ function LogsComponent({
203222
setTimeout(() => setLogsCleared(false), 100)
204223
}
205224

225+
const handleDownloadLogs = () => {
226+
const nodeName = podContainerOptions.podOptions[0].name
227+
if (isResourceBrowserView) {
228+
for (const _co of podContainerOptions.containerOptions) {
229+
if (_co.selected) {
230+
downloadLogs(
231+
setDownloadInProgress,
232+
appDetails,
233+
nodeName,
234+
_co.name,
235+
prevContainer,
236+
logsShownOption.current,
237+
selectedCustomLogFilter,
238+
isResourceBrowserView,
239+
selectedResource.clusterId,
240+
selectedResource.namespace,
241+
)
242+
}
243+
}
244+
} else {
245+
const selectedPods = podContainerOptions.podOptions
246+
.filter((_pod) => _pod.selected)
247+
.flatMap((_pod) => getSelectedPodList(_pod.name))
248+
249+
const containers = podContainerOptions.containerOptions.filter((_co) => _co.selected).map((_co) => _co.name)
250+
const podsWithContainers = selectedPods
251+
.flatMap((_pod) => flatContainers(_pod).map((_container) => [_pod.name, _container]))
252+
.filter((_pwc) => containers.includes(_pwc[1]))
253+
254+
for (const _pwc of podsWithContainers) {
255+
downloadLogs(
256+
setDownloadInProgress,
257+
appDetails,
258+
_pwc[0],
259+
_pwc[1],
260+
prevContainer,
261+
logsShownOption.current,
262+
selectedCustomLogFilter,
263+
)
264+
}
265+
}
266+
}
267+
206268
const fetchLogs = () => {
207269
if (podContainerOptions.podOptions.length == 0 || podContainerOptions.containerOptions.length == 0) {
208270
return
@@ -226,6 +288,8 @@ function LogsComponent({
226288
Host,
227289
_co.name,
228290
prevContainer,
291+
logsShownOption.current,
292+
selectedCustomLogFilter,
229293
isResourceBrowserView,
230294
selectedResource.clusterId,
231295
selectedResource.namespace,
@@ -245,14 +309,23 @@ function LogsComponent({
245309

246310
for (const _pwc of podsWithContainers) {
247311
pods.push(_pwc[0])
248-
urls.push(getLogsURL(appDetails, _pwc[0], Host, _pwc[1], prevContainer))
312+
urls.push(
313+
getLogsURL(
314+
appDetails,
315+
_pwc[0],
316+
Host,
317+
_pwc[1],
318+
prevContainer,
319+
logsShownOption.current,
320+
selectedCustomLogFilter,
321+
),
322+
)
249323
}
250324

251325
if (urls.length == 0) {
252326
return
253327
}
254328
}
255-
256329
workerRef.current['postMessage' as any]({
257330
type: 'start',
258331
payload: {
@@ -262,6 +335,7 @@ function LogsComponent({
262335
pods: pods,
263336
},
264337
})
338+
setNewFilteredLogs(false)
265339
}
266340

267341
const handleCurrentSearchTerm = (searchTerm: string): void => {
@@ -284,6 +358,18 @@ function LogsComponent({
284358
}
285359
}
286360

361+
const handleLogOptionChange = (selected) => {
362+
setLogsShownOption({
363+
prev: logsShownOption.current,
364+
current: selected,
365+
})
366+
if (selected.value !== CUSTOM_LOGS_FILTER.CUSTOM) {
367+
setNewFilteredLogs(true)
368+
} else {
369+
setShowCustomOptionsMoadal(true)
370+
}
371+
}
372+
287373
useEffect(() => {
288374
logsPausedRef.current = logsPaused
289375
}, [logsPaused])
@@ -333,7 +419,7 @@ function LogsComponent({
333419
fetchLogs()
334420

335421
return () => stopWorker()
336-
}, [logState, prevContainer])
422+
}, [logState, prevContainer, newFilteredLogs])
337423

338424
const podContainerOptions = getPodContainerOptions(
339425
isLogAnalyzer,
@@ -577,6 +663,40 @@ function LogsComponent({
577663
>
578664
<span className="fs-12 ">Prev. container</span>
579665
</Checkbox>
666+
<div className="h-16 dc__border-right ml-8 mr-8"></div>
667+
<LinesIcon className="icon-dim-16 mr-8" />
668+
<Select
669+
options={getPodLogsOptions()}
670+
onChange={handleLogOptionChange}
671+
value={logsShownOption.current}
672+
styles={{
673+
...multiSelectStyles,
674+
...podsDropdownStyles,
675+
}}
676+
components={{
677+
IndicatorSeparator: null,
678+
Option: (props) => <Option {...props} />,
679+
}}
680+
/>
681+
<div className="h-16 dc__border-right ml-8 mr-8"></div>
682+
{downloadInProgress ? (
683+
<Progressing
684+
size={16}
685+
styles={{ display: 'flex', justifyContent: 'flex-start', width: 'max-content' }}
686+
/>
687+
) : (
688+
<Tippy className="default-tt" arrow={false} placement="top" content={'Download logs'}>
689+
<Download
690+
className={`icon-dim-16 mr-8 cursor ${
691+
(podContainerOptions?.containerOptions ?? []).length === 0 ||
692+
(prevContainer && showNoPrevContainer != '')
693+
? 'cursor-not-allowed dc__opacity-0_5'
694+
: ''
695+
}`}
696+
onClick={handleDownloadLogs}
697+
/>
698+
</Tippy>
699+
)}
580700
</div>
581701
<div className="dc__border-right "></div>
582702
<form
@@ -721,6 +841,16 @@ function LogsComponent({
721841
/>
722842
</div>
723843
)}
844+
845+
{showCustomOptionsModal && (
846+
<CustomLogsModal
847+
setSelectedCustomLogFilter={setSelectedCustomLogFilter}
848+
selectedCustomLogFilter={selectedCustomLogFilter}
849+
setLogsShownOption={setLogsShownOption}
850+
setNewFilteredLogs={setNewFilteredLogs}
851+
setShowCustomOptionsMoadal={setShowCustomOptionsMoadal}
852+
/>
853+
)}
724854
</React.Fragment>
725855
)
726856
}

src/components/v2/appDetails/k8Resource/nodeDetail/NodeDetailTabs/node.type.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Moment } from 'moment'
2+
import { CUSTOM_LOGS_FILTER } from '../../../../../../config'
13
import { SocketConnectionType } from '../../../../../ClusterNodes/constants'
24
import { SelectedResourceType } from '../../../appDetails.type'
35

@@ -41,3 +43,20 @@ export interface PodEventsType {
4143
errorReason: string
4244
eventsResponse: any
4345
}
46+
47+
export interface SelectedCustomLogFilterType {
48+
option: string
49+
value: string
50+
unit?: string
51+
}
52+
53+
export interface CustomLogFilterOptionsType {
54+
[CUSTOM_LOGS_FILTER.DURATION]: { value: string; unit: string; error: string }
55+
[CUSTOM_LOGS_FILTER.LINES]: { value: string; error: string }
56+
[CUSTOM_LOGS_FILTER.SINCE]: {
57+
value: string
58+
date: Moment
59+
time: { label: string; value: string; isDisabled?: boolean }
60+
}
61+
[CUSTOM_LOGS_FILTER.ALL]: { value: string }
62+
}

0 commit comments

Comments
 (0)