Line data Source code
1 43 : import React from 'react';
2 :
3 : import { Button } from "@patternfly/react-core/dist/esm/components/Button";
4 : import { FormGroup } from "@patternfly/react-core/dist/esm/components/Form";
5 : import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect";
6 : import { Popover } from "@patternfly/react-core/dist/esm/components/Popover";
7 : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput";
8 : import { Grid } from "@patternfly/react-core/dist/esm/layouts/Grid";
9 : import { OutlinedQuestionCircleIcon, TrashIcon } from '@patternfly/react-icons';
10 : import { FormHelper } from "cockpit-components-form-helper.jsx";
11 43 : import ipaddr from "ipaddr.js";
12 :
13 : import cockpit from 'cockpit';
14 :
15 : import * as utils from './util.js';
16 :
17 43 : const _ = cockpit.gettext;
18 :
19 43 : const MAX_PORT = 65535;
20 :
21 5 : export function validatePublishPort(value, key) {
22 5 : switch (key) {
23 5 : case "IP":
24 5 : if (value && !ipaddr.isValid(value))
25 1 : return _("Must be a valid IP address");
26 5 : break;
27 5 : case "hostPort": {
28 5 : if (value) {
29 5 : const hostPort = parseInt(value);
30 5 : if (hostPort < 1 || hostPort > MAX_PORT)
31 1 : return _("1 to 65535");
32 5 : }
33 :
34 5 : break;
35 5 : }
36 5 : case "containerPort": {
37 5 : if (!value)
38 1 : return _("Container port must not be empty");
39 :
40 5 : const containerPort = parseInt(value);
41 5 : if (containerPort < 1 || containerPort > MAX_PORT)
42 3 : return _("1 to 65535");
43 :
44 5 : break;
45 5 : }
46 0 : default:
47 0 : console.error(`Unknown key "${key}"`); // not-covered: unreachable assertion
48 5 : }
49 5 : }
50 :
51 5 : export const PublishPort = ({ id, item, onChange, idx, removeitem, _itemCount, validationFailed, onValidationChange }) =>
52 : (
53 5 : <Grid hasGutter id={id}>
54 5 : <FormGroup className="pf-m-5-col-on-md"
55 5 : id={id + "-ip-address-group"}
56 5 : label={_("IP address")}
57 5 : fieldId={id + "-ip-address"}
58 5 : labelHelp={
59 5 : <Popover aria-label={_("IP address help")}
60 5 : enableFlip
61 5 : bodyContent={_("If host IP is set to 0.0.0.0 or not set at all, the port will be bound on all IPs on the host.")}>
62 5 : <Button variant="plain" hasNoPadding aria-label="More info" icon={<OutlinedQuestionCircleIcon />} />
63 5 : </Popover>
64 : }>
65 5 : <TextInput id={id + "-ip-address"}
66 5 : value={item.IP || ''}
67 1 : validated={validationFailed?.IP ? "error" : "default"}
68 5 : onChange={(_event, value) => {
69 5 : utils.validationClear(validationFailed, "IP", onValidationChange);
70 1 : utils.validationDebounce(() => onValidationChange({ ...validationFailed, IP: validatePublishPort(value, "IP") }));
71 5 : onChange(idx, 'IP', value);
72 5 : }} />
73 5 : <FormHelper helperTextInvalid={validationFailed?.IP} />
74 5 : </FormGroup>
75 5 : <FormGroup className="pf-m-2-col-on-md"
76 5 : id={id + "-host-port-group"}
77 5 : label={_("Host port")}
78 5 : fieldId={id + "-host-port"}
79 5 : labelHelp={
80 5 : <Popover aria-label={_("Host port help")}
81 5 : enableFlip
82 5 : bodyContent={_("If the host port is not set the container port will be randomly assigned a port on the host.")}>
83 5 : <Button variant="plain" hasNoPadding aria-label="More info" icon={<OutlinedQuestionCircleIcon />} />
84 5 : </Popover>
85 : }>
86 5 : <TextInput id={id + "-host-port"}
87 5 : type='number'
88 5 : step={1}
89 5 : min={1}
90 5 : max={MAX_PORT}
91 5 : value={item.hostPort || ''}
92 1 : validated={validationFailed?.hostPort ? "error" : "default"}
93 5 : onChange={(_event, value) => {
94 5 : utils.validationClear(validationFailed, "hostPort", onValidationChange);
95 1 : utils.validationDebounce(() => onValidationChange({ ...validationFailed, hostPort: validatePublishPort(value, "hostPort") }));
96 5 : onChange(idx, 'hostPort', value);
97 5 : }} />
98 5 : <FormHelper helperTextInvalid={validationFailed?.hostPort} />
99 5 : </FormGroup>
100 5 : <FormGroup className="pf-m-3-col-on-md"
101 5 : id={id + "-container-port-group"}
102 5 : label={_("Container port")}
103 5 : fieldId={id + "-container-port"} isRequired>
104 5 : <TextInput id={id + "-container-port"}
105 5 : type='number'
106 5 : step={1}
107 5 : min={1}
108 5 : max={MAX_PORT}
109 3 : validated={validationFailed?.containerPort ? "error" : "default"}
110 5 : value={item.containerPort || ''}
111 5 : onChange={(_event, value) => {
112 5 : utils.validationClear(validationFailed, "containerPort", onValidationChange);
113 1 : utils.validationDebounce(() => onValidationChange({ ...validationFailed, containerPort: validatePublishPort(value, "containerPort") }));
114 5 : onChange(idx, 'containerPort', value);
115 5 : }} />
116 5 : <FormHelper helperTextInvalid={validationFailed?.containerPort} />
117 5 : </FormGroup>
118 5 : <FormGroup className="pf-m-2-col-on-md"
119 5 : label={_("Protocol")}
120 5 : fieldId={id + "-protocol"}>
121 5 : <FormSelect className='pf-v6-c-form-control container-port-protocol'
122 5 : id={id + "-protocol"}
123 5 : value={item.protocol}
124 4 : onChange={(_event, value) => onChange(idx, 'protocol', value)}>
125 5 : <FormSelectOption value='tcp' key='tcp' label={_("TCP")} />
126 5 : <FormSelectOption value='udp' key='udp' label={_("UDP")} />
127 5 : </FormSelect>
128 5 : </FormGroup>
129 5 : <FormGroup className="pf-m-1-col-on-md remove-button-group">
130 5 : <Button variant='plain'
131 5 : className="btn-close"
132 5 : id={id + "-btn-close"}
133 5 : size="sm"
134 5 : aria-label={_("Remove item")}
135 5 : icon={<TrashIcon />}
136 4 : onClick={() => removeitem(idx)} />
137 5 : </FormGroup>
138 5 : </Grid>
139 : );
|