Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
04ca8fe
wip: security recommendation modal
shivani170 Mar 25, 2026
95dac0f
chore: code refactoring
shivani170 Mar 25, 2026
6111941
chore: css fixes
shivani170 Mar 25, 2026
b14586e
chore: scroll fix
shivani170 Mar 25, 2026
014006e
chore: hadolint tool added in the security
shivani170 Mar 26, 2026
fb223f9
chore: css fixes
shivani170 Mar 26, 2026
6c06187
chore: css fixes
shivani170 Mar 26, 2026
dc5151b
chore: css fixes
shivani170 Mar 30, 2026
4c4b8d4
fix: dockerfileScanEnabled FORCE_DOCKERFILE_SCAN cm integration
shivani170 Mar 30, 2026
a0ce557
chore: version bump
shivani170 Mar 31, 2026
3307541
chore: css fixes
shivani170 Mar 31, 2026
0509955
chore: info integration fixes
shivani170 Mar 31, 2026
cc895cf
chore: forceDockerfileScan added
shivani170 Mar 31, 2026
bd58740
chore: version bump
shivani170 Mar 31, 2026
d910c1e
chore: css fixes
shivani170 Mar 31, 2026
c7168a6
chore: docker scanned title fix
shivani170 Mar 31, 2026
a8749e4
chore: css fixes for loading state
shivani170 Mar 31, 2026
46b0e6f
fix: app env status in expanded state
arunjaindev Apr 1, 2026
1a9235d
Merge pull request #3086 from devtron-labs/fix/expanded-app-status
arunjaindev Apr 1, 2026
0d30f9c
chore: css fixes
shivani170 Apr 1, 2026
dbd68a3
Merge branch 'develop' into feat/dockerfile-hadolint-scan
shivani170 Apr 1, 2026
e115173
Merge branch 'main' into feat/dockerfile-hadolint-scan
shivani170 Apr 1, 2026
8a0a646
chore: version bump
shivani170 Apr 1, 2026
c3d3e75
Merge branch 'feat/dockerfile-hadolint-scan' of https://github.com/de…
shivani170 Apr 1, 2026
62de89f
chore: css fixes
shivani170 Apr 1, 2026
8e952d7
chore: polling added in the api
shivani170 Apr 1, 2026
46d30dc
chore: css fixes for error state
shivani170 Apr 1, 2026
e5f9c98
chore: FORCE_DOCKERFILE_SCAN removed from FE
shivani170 Apr 6, 2026
f1e8569
chore: mandatory check removing
shivani170 Apr 6, 2026
e43c187
chore: dockerfileScanEnabled check fix
shivani170 Apr 6, 2026
5ac7e9f
feat: dockerfile scan recommendation removed from dashboard
shivani170 Apr 6, 2026
bf3bf50
chore: dockerfile scan flag fix
shivani170 Apr 6, 2026
9113d58
chore: disabled state implemented for dockerfile toggle
shivani170 Apr 6, 2026
399c150
chore: initial value of dockerfileScanEnabled fixed
shivani170 Apr 6, 2026
4b5170d
chore: version bump
shivani170 Apr 7, 2026
d6eab29
chore: version bump
shivani170 Apr 7, 2026
b72b89d
chore: scanEnabled flag integration
shivani170 Apr 7, 2026
67962cb
Merge branch 'develop' into feat/dockerfile-hadolint-scan
shivani170 Apr 7, 2026
edc39e0
chore: fixed polling for failed state
shivani170 Apr 7, 2026
cf408f3
chore: fix for disable state
shivani170 Apr 7, 2026
93abb0a
chore: polling fix
shivani170 Apr 7, 2026
8ca735c
chore: fix for polling status 1
shivani170 Apr 7, 2026
b67d3bc
chore: version bump
shivani170 Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
| ENABLE_CHART_SEARCH_IN_HELM_DEPLOY | "true" | Enable chart search in Helm deploy |
| ENABLE_EXTERNAL_ARGO_CD | "true" | Enable External Argo CD |
| ENABLE_SCOPED_VARIABLES | "false" | For enabling scoped variable from UI, also need to enable it in backend. |
| FORCE_SECURITY_SCANNING | "false" | Force security scanning |
| FORCE_SECURITY_SCANNING | "false" | Force security |
| scanning | |
| GA_ENABLED | "true" | Enable Google Analytics (GA) |
| GA_TRACKING_ID | G-XXXXXXXX | Google Analytics tracking ID |
| GLOBAL_API_TIMEOUT | 60000 | Default timeout for all API requests in DASHBOARD |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
"@devtron-labs/devtron-fe-common-lib": "1.23.4-pre-0",
"@devtron-labs/devtron-fe-common-lib": "1.23.4-beta-4",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
Expand Down
87 changes: 53 additions & 34 deletions src/components/CIPipelineN/Build.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
CiPipelineSourceTypeOption,
CustomInput,
SourceTypeMap,
Icon,
} from '@devtron-labs/devtron-fe-common-lib'
import { ViewType } from '../../config'
import { createWebhookConditionList } from '../ciPipeline/ciPipeline.service'
Expand Down Expand Up @@ -166,6 +167,12 @@ export const Build = ({
setFormData(_formData)
}

const handleDockerfileScanToggle = (): void => {
const _formData = { ...formData }
_formData.dockerfileScanEnabled = !_formData.dockerfileScanEnabled
setFormData(_formData)
}

const renderBasicCI = () => {
const _webhookData: WebhookCIProps = {
webhookConditionList: formData.webhookConditionList,
Expand Down Expand Up @@ -206,53 +213,65 @@ export const Build = ({

const renderPipelineName = () => {
return (
<label className="form__row">
<CustomInput
name="name"
label="Pipeline Name"
disabled={!!ciPipeline?.id}
placeholder="e.g. my-first-pipeline"
type="text"
value={formData.name}
onChange={handlePipelineName}
required
error={formDataErrorObj.name && !formDataErrorObj.name.isValid && formDataErrorObj.name.message}
/>
</label>
<CustomInput
name="name"
label="Pipeline Name"
disabled={!!ciPipeline?.id}
placeholder="e.g. my-first-pipeline"
type="text"
value={formData.name}
onChange={handlePipelineName}
required
error={formDataErrorObj.name && !formDataErrorObj.name.isValid && formDataErrorObj.name.message}
/>
)
}

const renderScanner = () => (
<>
<hr />
<div>
<div
className="en-2 bw-1 br-4 pt-12 pb-12 pl-16 pr-12"
style={{ display: 'grid', gridTemplateColumns: '52px auto 32px' }}
>
<BugScanner />
<div>
<p className="fs-13 lh-20 fw-6 cn-9 mb-4">Scan for vulnerabilities</p>
<p className="fs-13 lh-18 mb-0 fs-12">Perform security scan after container image is built.</p>
</div>
<DTSwitch
isDisabled={window._env_.FORCE_SECURITY_SCANNING && formData.scanEnabled}
ariaLabel="Toggle scan for security vulnerabilities"
isChecked={formData.scanEnabled}
onChange={handleScanToggle}
name="create-build-pipeline-scan-vulnerabilities-toggle"
/>
<div className="en-2 bw-1 br-4 p-16 flexbox-col dc__gap-16">
<div style={{ display: 'grid', gridTemplateColumns: '52px auto 32px' }}>
<div className="flex icon-dim-40 scan-icon-wrapper">
<Icon name="ic-bg-scan" size={30} color={null} />
</div>
<div>
<p className="fs-13 lh-20 fw-6 cn-9 mb-4">Scan for vulnerabilities</p>
<p className="fs-13 lh-18 mb-0 fs-12">Perform security scan after container image is built.</p>
</div>
<DTSwitch
isDisabled={window._env_.FORCE_SECURITY_SCANNING && formData.scanEnabled}
ariaLabel="Toggle scan for security vulnerabilities"
isChecked={formData.scanEnabled}
onChange={handleScanToggle}
name="create-build-pipeline-scan-vulnerabilities-toggle"
/>
</div>
<div className="dc__border-bottom dc__secondary" />
Comment thread
shivani170 marked this conversation as resolved.
Outdated
<div style={{ display: 'grid', gridTemplateColumns: '52px auto 32px' }}>
<div className="icon-dim-40 scan-icon-wrapper flex">
<Icon name="ic-bg-docker-scanner" size={30} color={null} />
</div>
<div>
<p className="fs-13 lh-20 fw-6 cn-9 mb-4">Scan for recommendations</p>
<p className="fs-13 lh-18 mb-0 fs-12">
Perform linting scan of your docker file and get recommended optimizations.
</p>
</div>
<DTSwitch
ariaLabel="Toggle scan for security vulnerabilities"
Comment thread
shivani170 marked this conversation as resolved.
Outdated
isChecked={formData.dockerfileScanEnabled}
onChange={handleDockerfileScanToggle}
name="create-build-pipeline-scan-vulnerabilities-toggle"
/>
</div>
</>
</div>
)

return pageState === ViewType.LOADING.toString() ? (
<div style={{ minHeight: '200px' }} className="flex">
<Progressing pageLoader />
</div>
) : (
<div className="p-20 ci-scrollable-content">
<div className="p-20 ci-scrollable-content flexbox-col dc__gap-16">
{renderBasicCI()}
{!isJobView && isAdvanced && (
<>
Expand Down
7 changes: 6 additions & 1 deletion src/components/CIPipelineN/CIPipeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
materials: [],
triggerType: window._env_.DEFAULT_CI_TRIGGER_TYPE_MANUAL ? TriggerType.Manual : TriggerType.Auto,
scanEnabled: false,
dockerfileScanEnabled: false,
gitHost: undefined,
webhookEvents: [],
ciPipelineSourceTypeOptions: [],
Expand Down Expand Up @@ -616,7 +617,10 @@
validateStage(BuildStageVariable.Build, formData)
validateStage(BuildStageVariable.PostBuild, formData)
const scanValidation =
isJobCard || !isSecurityModuleInstalled || formData.scanEnabled || !window._env_.FORCE_SECURITY_SCANNING
isJobCard ||
!isSecurityModuleInstalled ||
formData.scanEnabled ||
!window._env_.FORCE_SECURITY_SCANNING

Check warning on line 623 in src/components/CIPipelineN/CIPipeline.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=devtron-labs_dashboard&issues=AZ1IiHceMsoPwz0Fh_42&open=AZ1IiHceMsoPwz0Fh_42&pullRequest=3084
if (!scanValidation) {
setApiInProgress(false)
ToastManager.showToast({
Expand Down Expand Up @@ -679,6 +683,7 @@
...formData,
materials: _materials,
scanEnabled: !isJobCard && isSecurityModuleInstalled ? formData.scanEnabled : false,
dockerfileScanEnabled: !isJobCard && isSecurityModuleInstalled ? formData.dockerfileScanEnabled : false,
},
_ciPipeline,
Comment thread
shivani170 marked this conversation as resolved.
_materials,
Expand Down
47 changes: 44 additions & 3 deletions src/components/app/details/appDetails/AppSecurity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@
* limitations under the License.
*/

import { getSecurityScan, useAsync } from '@devtron-labs/devtron-fe-common-lib'
import { useEffect } from 'react'

import { UseGetAppSecurityDetailsProps, UseGetAppSecurityDetailsReturnType } from './appDetails.type'
import { getSecurityScan, getSecurityScanRecommendations, useAsync } from '@devtron-labs/devtron-fe-common-lib'

import {
UseGetAppSecurityDetailsProps,
UseGetAppSecurityDetailsReturnType,
UseSecurityRecommendationReturnType,
} from './appDetails.type'

const SECURITY_SCAN_RECOMMENDATIONS_POLLING_INTERVAL = 3000

export const useGetAppSecurityDetails = ({
appId,
Expand All @@ -26,7 +34,7 @@
}: UseGetAppSecurityDetailsProps): UseGetAppSecurityDetailsReturnType => {
const [scanResultLoading, scanResultResponse, scanResultError, reloadScanResult] = useAsync(
() => getSecurityScan({ appId, envId, artifactId, installedAppId }),
[appId, envId, installedAppId],
[appId, envId, artifactId, installedAppId],
!!appId || !!installedAppId,
)

Expand All @@ -37,3 +45,36 @@
reloadScanResult,
}
}

export const useGetAppSecurityDetailsRecommendations = ({
appId,
buildId,
}: UseGetAppSecurityDetailsProps): UseSecurityRecommendationReturnType => {
const [
scanRecommendationsResultLoading,
scanRecommendationsResultResponse,
scanRecommendationsResultError,
reloadScanRecommendationsResult,
] = useAsync(() => getSecurityScanRecommendations({ appId, buildId }), [appId, buildId], !!appId && !!buildId)

useEffect(() => {
if (!appId || !buildId || scanRecommendationsResultResponse?.result?.status !== 0) {
return undefined
}

const timeoutId = window.setTimeout(() => {

Check warning on line 65 in src/components/app/details/appDetails/AppSecurity.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=devtron-labs_dashboard&issues=AZ1I7sEI3JBpSDiAt9Q2&open=AZ1I7sEI3JBpSDiAt9Q2&pullRequest=3084
reloadScanRecommendationsResult()
}, SECURITY_SCAN_RECOMMENDATIONS_POLLING_INTERVAL)

return () => {
window.clearTimeout(timeoutId)

Check warning on line 70 in src/components/app/details/appDetails/AppSecurity.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=devtron-labs_dashboard&issues=AZ1I7sEI3JBpSDiAt9Q3&open=AZ1I7sEI3JBpSDiAt9Q3&pullRequest=3084
}
}, [appId, buildId, reloadScanRecommendationsResult, scanRecommendationsResultResponse?.result?.status])

return {
scanRecommendationsResultLoading,
scanRecommendationsResultResponse,
scanRecommendationsResultError,
reloadScanRecommendationsResult,
}
}
35 changes: 35 additions & 0 deletions src/components/app/details/appDetails/appDetails.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ import {
AppEnvironment,
DeploymentStatusDetailsBreakdownDataType,
EnvAppsMetaDTO,
FiltersTypeEnum,
OptionType,
ResponseType,
ScanRecommendationsDTO,
ScanResultDTO,
SelectPickerProps,
ServerErrors,
TableCellComponentProps,
} from '@devtron-labs/devtron-fe-common-lib'

import { AggregatedNodes, UseGetDTAppDetailsReturnType } from '../../types'
import {
SecurityScanRecommendationRowTypes,
SecurityScanRecommendationTableAdditionalProps,
} from '../cIDetails/DockerfileScanRecommendation/types'

export enum AppMetricsTab {
Aggregate = 'aggregate',
Expand Down Expand Up @@ -182,13 +189,20 @@ export interface UseGetAppSecurityDetailsProps {
envId?: number
installedAppId?: number
artifactId?: number
buildId?: number
}
export interface UseGetAppSecurityDetailsReturnType {
scanResultLoading: boolean
scanResultResponse: ResponseType<ScanResultDTO>
scanResultError: ServerErrors
reloadScanResult: () => void
}
export interface UseSecurityRecommendationReturnType {
scanRecommendationsResultLoading: boolean
scanRecommendationsResultResponse: ResponseType<ScanRecommendationsDTO>
scanRecommendationsResultError: ServerErrors
reloadScanRecommendationsResult: () => void
}

export enum HibernationModalTypes {
HIBERNATE = 'hibernate',
Expand Down Expand Up @@ -218,3 +232,24 @@ export type AppEnvSelectorProps =
applications: EnvAppsMetaDTO['apps']
environments?: never
}

export type SecurityScanRecommendationColumn = {
label: string
field: string
size: { fixed: number } | null
CellComponent: (
props: TableCellComponentProps<
SecurityScanRecommendationRowTypes,
FiltersTypeEnum.URL,
SecurityScanRecommendationTableAdditionalProps
>,
) => JSX.Element | null
} & (
| {
isSortable: true
comparator: (a: unknown, b: unknown) => number
}
| {
isSortable?: false
}
)
58 changes: 6 additions & 52 deletions src/components/app/details/cIDetails/CIDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ import {
EMPTY_STATE_STATUS,
TabGroup,
TRIGGER_STATUS_PROGRESSING,
ErrorScreenManager,
SecurityDetailsCards,
sanitizeTargetPlatforms,
} from '@devtron-labs/devtron-fe-common-lib'
import { Switch, Route, Redirect, useRouteMatch, useParams, useHistory, generatePath } from 'react-router-dom'
Expand All @@ -54,18 +52,15 @@ import {
getArtifactForJobCi,
} from '../../service'
import { URLS, Routes } from '../../../../config'
import { BuildDetails, CIPipeline, HistoryLogsType, SecurityTabType } from './types'
import { ImageNotScannedView, CIRunningView } from './cIDetails.util'
import { BuildDetails, CIPipeline, HistoryLogsType } from './types'
import './ciDetails.scss'
import { getModuleInfo } from '../../../v2/devtronStackManager/DevtronStackManager.service'
import { ModuleStatus } from '../../../v2/devtronStackManager/DevtronStackManager.type'
import { getModuleConfigured } from '../appDetails/appDetails.service'
import { CIPipelineBuildType } from '../../../ciPipeline/types'
import { renderCIListHeader, renderDeploymentHistoryTriggerMetaText } from '../cdDetails/utils'
import { importComponentFromFELibrary } from '@Components/common'
import { useGetAppSecurityDetails } from '../appDetails/AppSecurity'
import { SecurityTab } from './SecurityTab'

const SecurityModalSidebar = importComponentFromFELibrary('SecurityModalSidebar', null, 'function')
const terminalStatus = new Set(['succeeded', 'failed', 'error', 'cancelled', 'nottriggered', 'notbuilt'])
const statusSet = new Set(['starting', 'running', 'pending'])

Expand Down Expand Up @@ -219,7 +214,9 @@ export default function CIDetails({ isJobView, filteredEnvIds }: { isJobView?: b
const pipeline = pipelinesMap.get(+pipelineId)

const redirectToArtifactLogs = () => {
push(`${URLS.APPLICATION_MANAGEMENT_APP}/${pipeline.parentAppId}/${URLS.APP_CI_DETAILS}/${pipeline.parentCiPipeline}/logs`)
push(
`${URLS.APPLICATION_MANAGEMENT_APP}/${pipeline.parentAppId}/${URLS.APP_CI_DETAILS}/${pipeline.parentCiPipeline}/logs`,
)
}
const renderSourcePipelineButton = () => {
return (
Expand Down Expand Up @@ -484,7 +481,7 @@ export const Details = ({
? [
{
id: 'security-tab',
label: 'Security',
label: 'Reports',
tabType: 'navLink' as const,
props: {
to: 'security',
Expand Down Expand Up @@ -649,46 +646,3 @@ const HistoryLogs = ({
)
}

const SecurityTab = ({ artifactId, status, appIdFromParent }: SecurityTabType) => {
const { appId } = useParams<{ appId: string }>()

const computedAppId = appId ?? appIdFromParent

const { scanResultLoading, scanResultResponse, scanResultError, reloadScanResult } = useGetAppSecurityDetails({
appId: +computedAppId,
artifactId,
})

if (['starting', 'running'].includes(status.toLowerCase())) {
return <CIRunningView isSecurityTab />
}

if (!artifactId) {
return (
<GenericEmptyState
title={EMPTY_STATE_STATUS.ARTIFACTS_EMPTY_STATE_TEXTS.NoArtifactsGenerated}
subTitle={EMPTY_STATE_STATUS.ARTIFACTS_EMPTY_STATE_TEXTS.NoArtifactsError}
/>
)
}

if (scanResultLoading) {
return (
<div className="bg__primary flex-grow-1">
<Progressing pageLoader />
</div>
)
}
if (scanResultError) {
return <ErrorScreenManager code={scanResultError.code} reload={reloadScanResult} />
}
if (!scanResultResponse?.result.scanned) {
return <ImageNotScannedView />
}

return (
<div className="p-20 bg__primary flex-grow-1">
<SecurityDetailsCards scanResult={scanResultResponse?.result} Sidebar={SecurityModalSidebar} />
</div>
)
}
Loading
Loading