Skip to content

Commit 159e317

Browse files
dennisvankekemsvcAPLBotferruhcihan
authored
feat: [APL-898] - Network Policies Page (#609)
* feat: network policies layout * feat: network policies multi tag component * fix: ingress * feat: podlabelrow * feat: target row and api changes * feat: label helper and multirow auto select * feat: flow + states * fix: ingress row styling * feat: flow renderer and fine tuning * fix: a lot of type errors * fix: get default label for target pod * fix: flatten array logic reworked * fix: more type errors * fix: remove the weird semicolon * feat: load workloads on edit page load * fix: egress page cleanup * fix: cleanup * fix: autocomplete component refactor * fix: navigation issues * fix: form and row validation * fix: egress goes to ingress page edit bug * fix: page name * Update src/pages/network-policies/overview/NetworkPoliciesOverviewPage.tsx Co-authored-by: Ferruh <63190600+ferruhcihan@users.noreply.github.com> * fix: copy paste mistake * feat: netpol overview columns * fix: loading egress ports data * fix: minor changes * fix: port number * fix: egress cleanup detail * fix: filter empty rows * fix: max character length for name * fix: removed react flow --------- Co-authored-by: svcAPLBot <174728082+svcAPLBot@users.noreply.github.com> Co-authored-by: Ferruh <63190600+ferruhcihan@users.noreply.github.com>
1 parent 506ff79 commit 159e317

19 files changed

+1284
-134
lines changed

public/i18n/en/common.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
"SealedSecret_plural": "Sealed Secrets",
4646
"Netpol": "Network Policy",
4747
"Netpol_plural": "Network Policies",
48+
"Inbound Rule_plural": "Inbound Rules",
49+
"Outbound Rule_plural": "Outbound Rules",
4850
"Secrets": "Secrets",
4951
"Service": "Service",
5052
"Service_plural": "Services",
@@ -104,5 +106,7 @@
104106
"Code-repository": "Code Repository",
105107
"Code-repository_plural": "Code Repositories",
106108
"TITLE_CODE_REPOSITORY": "Code repository details",
107-
"TITLE_CODE_REPOSITORIES": "Code repositories - {{scope}}"
109+
"TITLE_CODE_REPOSITORIES": "Code repositories - {{scope}}",
110+
"TITLE_NETWORK_POLICY": "Network policy details",
111+
"TITLE_NETWORK_POLICIES": "Network policies - {{scope}}"
108112
}

src/App.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { CacheProvider } from '@emotion/react'
55
import { CssBaseline } from '@mui/material'
66
import cookie from 'cookie'
77
import Backups from 'pages/Backups'
8-
import Netpols from 'pages/Netpols'
98
import Workloads from 'pages/Workloads'
109
import Build from 'pages/builds/create-edit'
1110
import Builds from 'pages/builds/overview'
@@ -31,7 +30,6 @@ import { Provider } from 'react-redux'
3130
import { Route, BrowserRouter as Router, Switch } from 'react-router-dom'
3231
import { store } from 'redux/store'
3332
import Backup from 'pages/Backup'
34-
import Netpol from 'pages/Netpol'
3533
import LoadingScreen from 'components/LoadingScreen'
3634
import Dashboard from 'pages/Dashboard'
3735
import Users from 'pages/Users'
@@ -53,6 +51,9 @@ import CodeRepository from 'pages/code-repositories/create-edit'
5351
import CodeRepositories from 'pages/code-repositories/overview'
5452
import SecretOverviewPage from 'pages/secrets/overview/SecretOverviewPage'
5553
import SecretCreateEditPage from 'pages/secrets/create-edit/SecretCreateEditPage'
54+
import NetworkPoliciesOverviewPage from 'pages/network-policies/overview/NetworkPoliciesOverviewPage'
55+
import NetworkPoliciesIngressCreateEditPage from 'pages/network-policies/create-edit/NetworkPoliciesIngressCreateEditPage'
56+
import NetworkPoliciesEgressCreateEditPage from 'pages/network-policies/create-edit/NetworkPoliciesEgressCreateEditPage'
5657
import { HttpErrorBadRequest } from './utils/error'
5758
import { NotistackProvider, SnackbarUtilsConfigurator } from './utils/snack'
5859

@@ -120,7 +121,12 @@ function App() {
120121
/>
121122
<PrivateRoute path='/clusters' component={Clusters} platformAdminRoute exact />
122123
<PrivateRoute path='/teams/create' component={Team} platformAdminRoute exact />
123-
<PrivateRoute path='/network-policies' component={Netpols} platformAdminRoute exact />
124+
<PrivateRoute
125+
path='/network-policies'
126+
component={NetworkPoliciesOverviewPage}
127+
platformAdminRoute
128+
exact
129+
/>
124130
<PrivateRoute path='/policies' component={Policies} platformAdminRoute exact />
125131
<PrivateRoute
126132
path='/policies/:policyName'
@@ -153,7 +159,16 @@ function App() {
153159
<PrivateRoute path='/teams' component={Teams} platformAdminRoute exact />
154160
<PrivateRoute path='/teams/:teamId' component={Team} exact />
155161
<PrivateRoute path='/teams/:teamId/backups/create' component={Backup} exact />
156-
<PrivateRoute path='/teams/:teamId/network-policies/create' component={Netpol} exact />
162+
<PrivateRoute
163+
path='/teams/:teamId/network-policies/inbound-rules/create'
164+
component={NetworkPoliciesIngressCreateEditPage}
165+
exact
166+
/>
167+
<PrivateRoute
168+
path='/teams/:teamId/network-policies/outbound-rules/create'
169+
component={NetworkPoliciesEgressCreateEditPage}
170+
exact
171+
/>
157172
<PrivateRoute
158173
path='/teams/:teamId/secrets/create'
159174
component={SecretCreateEditPage}
@@ -171,10 +186,19 @@ function App() {
171186
/>
172187
<PrivateRoute path='/teams/:teamId/backups' component={Backups} exact />
173188
<PrivateRoute path='/teams/:teamId/backups/:backupName' component={Backup} exact />
174-
<PrivateRoute path='/teams/:teamId/network-policies' component={Netpols} exact />
175189
<PrivateRoute
176-
path='/teams/:teamId/network-policies/:netpolName'
177-
component={Netpol}
190+
path='/teams/:teamId/network-policies'
191+
component={NetworkPoliciesOverviewPage}
192+
exact
193+
/>
194+
<PrivateRoute
195+
path='/teams/:teamId/network-policies/inbound-rules/:networkPolicyName'
196+
component={NetworkPoliciesIngressCreateEditPage}
197+
exact
198+
/>
199+
<PrivateRoute
200+
path='/teams/:teamId/network-policies/outbound-rules/:networkPolicyName'
201+
component={NetworkPoliciesEgressCreateEditPage}
178202
exact
179203
/>
180204
<PrivateRoute path='/teams/:teamId/projects' component={Projects} exact />

src/components/HeaderTitle.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const useStyles = makeStyles()((theme) => ({
1313
padding: '16px !important',
1414
},
1515
},
16+
headTitle: {
17+
fontSize: '1.5rem',
18+
},
1619
headBackground: {
1720
backgroundColor: theme.palette.background.default,
1821
},
@@ -46,7 +49,7 @@ export default function HeaderTitle({
4649
return (
4750
<>
4851
<div className={cx(classes.head, altColor ? classes.headBackgroundAlt : classes.headBackground)}>
49-
<Typography variant='h4' data-cy={`heading-${resourceTypeLow}`}>
52+
<Typography variant='h4' className={classes.headTitle} data-cy={`heading-${resourceTypeLow}`}>
5053
{title}
5154
</Typography>
5255
{docUrl && <HelpButton id='form' size='small' href={`${docUrl}`} />}

src/components/ListTable.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ export default function ({
3939
const { t } = useTranslation()
4040
// END HOOKS
4141
const resourceTypePlural = `${resourceType}_plural`
42-
let title
43-
if ((adminOnly || !teamId) && !hasTeamScope) title = t('LIST_TITLE_NOSCOPE', { model: t(resourceTypePlural) })
44-
if ((adminOnly || !teamId) && hasTeamScope) title = t('LIST_TITLE', { model: t(resourceTypePlural) })
45-
if (!adminOnly && teamId) title = t('LIST_TITLE_TEAM', { model: t(resourceTypePlural), teamId })
42+
const title = t('LIST_TITLE_NOSCOPE', { model: t(resourceTypePlural) })
4643
const resourceTypeLow = t(resourceTypePlural).replaceAll(' ', '-').toLowerCase()
4744
const redirect = to || (adminOnly ? `/${resourceTypeLow}/create` : `/teams/${oboTeamId}/${resourceTypeLow}/create`)
4845
return (
Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import MuiAutocomplete from '@mui/material/Autocomplete'
22
import React, { JSX, useState } from 'react'
3-
4-
import type { AutocompleteProps, AutocompleteRenderInputParams } from '@mui/material/Autocomplete'
53
import ArrowDropDownIcon from '@mui/icons-material/ExpandMore'
6-
import { TextField } from './TextField'
74

5+
import type {
6+
AutocompleteChangeDetails,
7+
AutocompleteChangeReason,
8+
AutocompleteProps,
9+
AutocompleteRenderInputParams,
10+
} from '@mui/material/Autocomplete'
11+
import { TextField } from './TextField'
812
import type { TextFieldProps } from './TextField'
913

1014
export interface EnhancedAutocompleteProps<
11-
T extends { label: string },
15+
T,
1216
Multiple extends boolean | undefined = undefined,
1317
DisableClearable extends boolean | undefined = undefined,
1418
FreeSolo extends boolean | undefined = undefined,
@@ -19,6 +23,8 @@ export interface EnhancedAutocompleteProps<
1923
helperText?: TextFieldProps['helperText']
2024
/** A required label for the Autocomplete to ensure accessibility. */
2125
label: string
26+
/** Optional field to hide label, mostly used in Key value components */
27+
hideLabel?: boolean
2228
/** Removes the top margin from the input label, if desired. */
2329
noMarginTop?: boolean
2430
/** Element to show when the Autocomplete search yields no results. */
@@ -27,76 +33,105 @@ export interface EnhancedAutocompleteProps<
2733
renderInput?: (_params: AutocompleteRenderInputParams) => React.ReactNode
2834
/** Label for the "select all" option. */
2935
selectAllLabel?: string
36+
/** Removes the "select all" option for multiselect */
37+
disableSelectAll?: boolean
3038
textFieldProps?: Partial<TextFieldProps>
3139
width?: 'small' | 'medium' | 'large'
3240
}
3341

34-
/**
35-
* An Autocomplete component that provides a user-friendly select input
36-
* allowing selection between options.
37-
*
38-
* @example
39-
* <Autocomplete
40-
* label="Select a Fruit"
41-
* onSelectionChange={(selected) => console.log(selected)}
42-
* options={[
43-
* {
44-
* label: 'Apple',
45-
* value: 'apple',
46-
* }
47-
* ]}
48-
* />
49-
*/
5042
export function Autocomplete<
51-
T extends { label: string },
43+
T,
5244
Multiple extends boolean | undefined = undefined,
5345
DisableClearable extends boolean | undefined = undefined,
5446
FreeSolo extends boolean | undefined = undefined,
5547
>(props: EnhancedAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>) {
5648
const {
5749
clearOnBlur,
5850
defaultValue,
59-
disablePortal = true,
51+
disablePortal = false,
6052
errorText = '',
6153
helperText,
54+
hideLabel = false,
6255
label,
6356
limitTags = 2,
6457
loading = false,
6558
loadingText,
59+
multiple,
60+
disableSelectAll = false,
6661
noOptionsText,
6762
onBlur,
6863
options,
6964
placeholder,
7065
renderInput,
66+
selectAllLabel = '',
7167
textFieldProps,
7268
value,
7369
onChange,
7470
width = 'medium',
7571
...rest
7672
} = props
73+
7774
const [inPlaceholder, setInPlaceholder] = useState('')
7875

76+
// --- select-all logic ---
77+
const isSelectAllActive = multiple && Array.isArray(value) && value.length === options.length
78+
79+
const selectAllText = isSelectAllActive ? 'Deselect All' : 'Select All'
80+
const selectAllOption = { label: `${selectAllText} ${selectAllLabel}` } as unknown as T
81+
const optionsWithSelectAll = [selectAllOption, ...options]
82+
83+
const handleChange: AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>['onChange'] = (
84+
e,
85+
newValue,
86+
reason: AutocompleteChangeReason,
87+
details?: AutocompleteChangeDetails<T>,
88+
) => {
89+
if (!onChange) return
90+
91+
// if they clicked the "Select All" option
92+
if (details?.option === selectAllOption) {
93+
const next = isSelectAllActive ? ([] as unknown as typeof newValue) : (options as unknown as typeof newValue)
94+
onChange(e, next, reason, details)
95+
} else onChange(e, newValue, reason, details)
96+
}
97+
// --------------------------
98+
7999
return (
80100
<MuiAutocomplete
81-
options={options}
101+
options={multiple && !disableSelectAll && options.length > 0 ? optionsWithSelectAll : options}
102+
multiple={multiple}
103+
disableCloseOnSelect={multiple}
104+
clearOnBlur={clearOnBlur}
105+
data-qa-autocomplete={label}
106+
defaultValue={defaultValue}
107+
disablePortal={disablePortal}
108+
limitTags={limitTags}
109+
loading={loading}
110+
loadingText={loadingText || 'Loading...'}
111+
noOptionsText={noOptionsText || <i>You have no options to choose from</i>}
112+
onBlur={onBlur}
113+
onOpen={() => setInPlaceholder('Search')}
114+
onClose={() => setInPlaceholder(placeholder || '')}
115+
popupIcon={<ArrowDropDownIcon />}
82116
renderInput={
83117
renderInput ||
84118
((params) => (
85119
<TextField
120+
hideLabel={hideLabel}
86121
label={label}
87122
width={width}
88123
loading={loading}
89-
placeholder={inPlaceholder || (placeholder ?? 'Select an option')}
124+
placeholder={inPlaceholder || placeholder || 'Select an option'}
90125
{...params}
91126
error={!!errorText}
92127
helperText={helperText}
93128
InputProps={{
94129
...params.InputProps,
95130
...textFieldProps?.InputProps,
96131
sx: {
97-
overflow: 'hidden',
98-
whiteSpace: 'nowrap',
99-
textOverflow: 'ellipsis',
132+
display: 'flex',
133+
flexWrap: 'wrap',
134+
gap: 1,
100135
paddingRight: '44px',
101136
},
102137
}}
@@ -106,21 +141,9 @@ export function Autocomplete<
106141
/>
107142
))
108143
}
109-
clearOnBlur={clearOnBlur}
110-
data-qa-autocomplete={label}
111-
defaultValue={defaultValue}
112-
disablePortal={disablePortal}
113-
limitTags={limitTags}
114-
loading={loading}
115-
loadingText={loadingText || 'Loading...'}
116-
noOptionsText={noOptionsText || <i>You have no options to choose from</i>}
117-
onBlur={onBlur}
118-
onOpen={() => setInPlaceholder('Search')}
119-
onClose={() => setInPlaceholder(placeholder || '')}
120-
popupIcon={<ArrowDropDownIcon />}
121144
value={value}
145+
onChange={handleChange}
122146
{...rest}
123-
onChange={onChange}
124147
/>
125148
)
126149
}

0 commit comments

Comments
 (0)