• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15163566361

21 May 2025 01:31PM UTC coverage: 76.93% (-0.06%) from 76.985%
15163566361

Pull #10950

github

web-flow
Merge 652f28b2c into 1fe44f98a
Pull Request #10950: Fix #10947 Updated dockerfile to static files for standard templates

30980 of 48267 branches covered (64.18%)

38608 of 50186 relevant lines covered (76.93%)

36.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.33
/web/client/components/manager/users/UserDialog.jsx
1
/**
2
 * Copyright 2016, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
*/
8

9
import PropTypes from 'prop-types';
10
import React from 'react';
11

12
import {
13
    Alert,
14
    Tabs,
15
    Tab,
16
    Glyphicon,
17
    Checkbox,
18
    FormControl,
19
    FormGroup,
20
    ControlLabel
21
} from 'react-bootstrap';
22

23
import tooltip from '../../../components/misc/enhancers/tooltip';
24
const GlyphiconTooltip = tooltip(Glyphicon);
1✔
25
import Dialog from '../../../components/misc/Dialog';
26
import UserGroups from './UserGroups';
27
import Message from '../../../components/I18N/Message';
28
import Spinner from 'react-spinkit';
29
import { findIndex, castArray } from 'lodash';
30
import CloseConfirmButton from './CloseConfirmButton';
31
import './style/userdialog.css';
32
import Button from '../../../components/misc/Button';
33
import controls from './AttributeControls';
34

35
/**
36
 * A Modal window to show password reset form
37
 */
38
class UserDialog extends React.Component {
39
    static propTypes = {
1✔
40
        // props
41
        user: PropTypes.object,
42
        groups: PropTypes.array,
43
        groupsStatus: PropTypes.string,
44
        show: PropTypes.bool,
45
        onClose: PropTypes.func,
46
        onChange: PropTypes.func,
47
        onSave: PropTypes.func,
48
        modal: PropTypes.bool,
49
        closeGlyph: PropTypes.string,
50
        style: PropTypes.object,
51
        buttonSize: PropTypes.string,
52
        inputStyle: PropTypes.object,
53
        attributeFields: PropTypes.array,
54
        minPasswordSize: PropTypes.number,
55
        hidePasswordFields: PropTypes.bool,
56
        buttonTooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
57
    };
58

59
    static defaultProps = {
1✔
60
        user: {},
61
        onClose: () => {},
62
        onChange: () => {},
63
        onSave: () => {},
64
        options: {},
65
        useModal: true,
66
        closeGlyph: "",
67
        style: {},
68
        buttonSize: "small",
69
        includeCloseButton: true,
70
        attributeFields: [{
71
            name: "email"
72
        }, {
73
            name: "company"
74
        }, {
75
            name: "notes",
76
            controlType: "text"
77
        }],
78
        inputStyle: {
79
            height: "32px",
80
            width: "260px",
81
            marginTop: "3px",
82
            marginBottom: "20px",
83
            padding: "5px",
84
            border: "1px solid #078AA3"
85
        },
86
        minPasswordSize: 6,
87
        hidePasswordFields: false
88
    };
89

90
    state = {
19✔
91
        key: 1
92
    };
93
    // Only to keep the selected button, not for the modal window
94
    getAttributes = () => {
19✔
95
        return this.props.user?.attribute || [];
102✔
96
    }
97
    getAttributeValue = (name) => {
19✔
98
        let attrs = this.getAttributes();
76✔
99
        if (attrs) {
76!
100
            let index = findIndex(attrs, a => a.name === name);
76✔
101
            return attrs[index] && attrs[index].value;
76✔
102
        }
103
        return null;
×
104
    };
105

106
    getPwStyle = () => {
19✔
107
        if (!this.props.user || !this.props.user.newPassword) {
25✔
108
            return null;
21✔
109
        }
110
        let pw = this.props.user.newPassword;
4✔
111
        const validation = this.isMainPasswordValid(pw);
4✔
112
        return validation.valid ? "success" : "error";
4!
113
    };
114

115
    getPwValidationMeta = () => {
19✔
116
        if (!this.props.user || !this.props.user.newPassword) {
25✔
117
            return {
21✔
118
                valid: true,
119
                message: "user.passwordMessage",
120
                args: null
121
            };
122
        }
123

124
        let pw = this.props.user.newPassword;
4✔
125
        const validation = this.isMainPasswordValid(pw);
4✔
126
        return validation;
4✔
127
    }
128

129
    renderPasswordFields = () => {
19✔
130
        const validation = this.getPwValidationMeta();
25✔
131
        const tooltipId = validation.message;
25✔
132
        const args = validation.args;
25✔
133

134
        return (
25✔
135
            <div>
136
                <FormGroup validationState={this.getPwStyle()}>
137
                    <ControlLabel><Message msgId="user.password"/>
138
                        {' '}<span style={{ fontWeight: 'bold' }}>*</span>
139
                        <GlyphiconTooltip tooltipId={tooltipId} args={args} tooltipPosition="right"
140
                            glyph="info-sign" style={{position: "relative", marginLeft: "10px", display: "inline-block", width: 24}}
141
                            helpText="Password must contain at least 6 characters"/>
142
                    </ControlLabel>
143
                    <FormControl ref="newPassword"
144
                        inputRef={node => {this.newPasswordField = node;}}
50✔
145
                        key="newPassword"
146
                        type="password"
147
                        name="newPassword"
148
                        autoComplete="new-password"
149
                        style={this.props.inputStyle}
150
                        onChange={this.handleChange} />
151
                </FormGroup>
152
                <FormGroup validationState={ (this.isValidPassword() ? "success" : "error") }>
25✔
153
                    <ControlLabel><Message msgId="user.retypePwd"/>{' '}<span style={{ fontWeight: 'bold' }}>*</span></ControlLabel>
154
                    <FormControl ref="confirmPassword"
155
                        inputRef={node => {this.confirmPasswordField = node;}}
50✔
156
                        key="confirmPassword"
157
                        name="confirmPassword"
158
                        type="password"
159
                        autoComplete="new-password"
160
                        style={this.props.inputStyle}
161
                        onChange={this.handleChange} />
162
                </FormGroup>
163
            </div>
164
        );
165
    };
166

167
    renderGeneral = () => {
19✔
168
        return (<div style={{clear: "both", marginTop: "10px"}}>
26✔
169
            <FormGroup>
170
                <ControlLabel><Message msgId="user.username"/>{' '}<span style={{ fontWeight: 'bold' }}>*</span></ControlLabel>
171
                <FormControl ref="name"
172
                    key="name"
173
                    type="text"
174
                    name="name"
175
                    readOnly={this.props.user && this.props.user.id}
50✔
176
                    style={this.props.inputStyle}
177
                    onChange={this.handleChange}
178
                    value={this.props.user && this.props.user.name || ""}/>
58✔
179
            </FormGroup>
180
            {this.props.hidePasswordFields ? null : this.renderPasswordFields() }
26✔
181
            <select name="role" style={this.props.inputStyle} onChange={this.handleChange} value={this.props.user && this.props.user.role || ""}>
53✔
182
                <option value="ADMIN">ADMIN</option>
183
                <option value="USER">USER</option>
184
            </select>
185
            <FormGroup>
186
                <ControlLabel style={{"float": "left", marginRight: "10px"}}><Message msgId="users.enabled"/></ControlLabel>
187
                <Checkbox
188
                    defaultChecked={this.props.user && (this.props.user.enabled === undefined ? false : this.props.user.enabled)}
74✔
189
                    type="checkbox"
190
                    key={"enabled" + (this.props.user ? this.props.user.enabled : "missing")}
26✔
191
                    name="enabled"
192
                    onClick={(evt) => {this.props.onChange("enabled", evt.target.checked ? true : false); }} />
2✔
193
            </FormGroup>
194
            <div style={{ fontStyle: 'italic' }}><Message msgId="users.requiredFiedsMessage"/></div>
195
        </div>);
196
    };
197
    renderAttributes = () => {
19✔
198
        const byName = attrName => ({ name }) => attrName === name;
26✔
199
        const attributes = this.getAttributes();
26✔
200
        return <>
26✔
201
            {
202
                this.props.attributeFields.map(({ name, title, controlType, ...rest }) => {
203
                    const value = this.getAttributeValue(name);
76✔
204
                    const Control = controls[controlType ?? "string"];
76✔
205
                    return (<div style={{ marginTop: "10px" }}>
76✔
206
                        <label key="member-label" className="control-label">{title ?? name}</label>
152✔
207
                        <Control
208
                            name={"attribute." + name}
209
                            {...rest}
210
                            value={value}
211
                            onChange={(newValue) => {
212
                                // newValue can be an array or a single value to support multiple attributes of the same name.
213
                                const newAttributes = attributes.filter(v => !byName(name)(v)).concat(castArray(newValue).map(vv => ({ name, value: vv })));
×
214
                                this.props.onChange("attribute", newAttributes);
×
215
                            }} />
216
                    </div>);
217
                })
218
            }
219
        </>;
220
    };
221

222
    renderSaveButtonContent = () => {
19✔
223
        let status = this.props.user && this.props.user.status;
26✔
224
        let defaultMessage = this.props.user && this.props.user.id ? <Message key="text" msgId="users.saveUser"/> : <Message key="text" msgId="users.createUser" />;
26✔
225
        let messages = {
26✔
226
            error: defaultMessage,
227
            success: defaultMessage,
228
            modified: defaultMessage,
229
            save: <Message key="text" msgId="users.saveUser"/>,
230
            saving: <Message key="text" msgId="users.savingUser" />,
231
            saved: <Message key="text" msgId="users.userSaved" />,
232
            creating: <Message key="text" msgId="users.creatingUser" />,
233
            created: <Message key="text" msgId="users.userCreated" />
234
        };
235
        let message = messages[status] || defaultMessage;
26✔
236
        return [this.isSaving() ? <Spinner key="saving-spinner" spinnerName="circle" noFadeIn overrideSpinnerClassName="spinner"/> : null, message];
26✔
237
    };
238

239
    handleSaveUser = () =>{
19✔
240
        this.props.onSave(this.props.user);
1✔
241
    }
242

243
    renderButtons = () => {
19✔
244
        let CloseBtn = <CloseConfirmButton status={this.props.user && this.props.user.status} onClick={this.close}/>;
26✔
245
        return [
26✔
246
            CloseBtn,
247
            <Button key="save" bsSize={this.props.buttonSize}
248
                bsStyle={this.isSaved() ? "success" : "primary" }
26✔
249
                onClick={this.handleSaveUser}
250
                disabled={!this.isValid() || this.isSaving()}>
27✔
251
                {this.renderSaveButtonContent()}</Button>
252
        ];
253
    };
254

255
    renderGroups = () => {
19✔
256
        return <UserGroups onUserGroupsChange={this.props.onChange} user={this.props.user} groups={this.props.groups} />;
26✔
257
    };
258

259
    renderError = () => {
19✔
260
        let error = this.props.user && this.props.user.status === "error";
26✔
261
        if ( error ) {
26!
262
            let lastError = this.props.user && this.props.user.lastError;
×
263
            return <Alert key="error" bsStyle="warning"><Message msgId="users.errorSaving" />{lastError && lastError.statusText}</Alert>;
×
264
        }
265
        return null;
26✔
266
    };
267

268
    render() {
269
        return (!this.props.show ? null : <Dialog modal draggable={false} maskLoading={this.props.user && (this.props.user.status === "loading" || this.props.user.status === "saving")} id="mapstore-user-dialog" className="user-edit-dialog" style={this.props.style}>
26!
270

271
            <span role="header">
272
                <span className="user-panel-title modal-title">{(this.props.user && this.props.user.name) || <Message msgId="users.newUser" />}</span>
58✔
273
                <button onClick={this.close} className="login-panel-close close">
274
                    {this.props.closeGlyph ? <Glyphicon glyph={this.props.closeGlyph}/> : <span><Glyphicon glyph="1-close"/></span>}
26!
275
                </button>
276
            </span>
277
            <div role="body">
278
                <Tabs justified defaultActiveKey={1} onSelect={ ( key) => { this.setState({key}); }} key="tab-panel" id="userDetails-tabs">
2✔
279
                    <Tab eventKey={1} title={<GlyphiconTooltip tooltipId="user.generalInformation" glyph="user" style={{ display: 'block', padding: 8 }}/>} >
280
                        {this.renderGeneral()}
281
                    </Tab>
282
                    <Tab eventKey={2} title={<GlyphiconTooltip tooltipId="user.attributes" glyph="info-sign" style={{ display: 'block', padding: 8 }}/>} >
283
                        {this.renderAttributes()}
284
                    </Tab>
285
                    <Tab eventKey={3} title={<GlyphiconTooltip tooltipId="groups" glyph="1-group" style={{ display: 'block', padding: 8 }}/>} >
286
                        {this.renderGroups()}
287
                    </Tab>
288
                </Tabs>
289
            </div>
290
            <div role="footer">
291
                {this.renderError()}
292
                {this.renderButtons()}
293
            </div>
294
        </Dialog>);
295
    }
296

297
    close = () => {
19✔
298
        if (this.newPasswordField) {
2!
299
            this.newPasswordField.value = '';
2✔
300
        }
301
        if (this.confirmPasswordField) {
2!
302
            this.confirmPasswordField.value = '';
2✔
303
        }
304

305
        this.props.onClose();
2✔
306
    }
307

308
    isMainPasswordValid = (password) => {
19✔
309
        const validation = {
36✔
310
            valid: true,
311
            message: "user.passwordMessage",
312
            args: null
313
        };
314
        let p = password || this.props.user.newPassword || "";
36✔
315

316
        // Empty password field will signal the GeoStoreDAO not to change the password
317
        if (p === "" && this.props.user && this.props.user.id) {
36✔
318
            return validation;
14✔
319
        }
320

321
        if (p.length < this.props.minPasswordSize) {
22✔
322
            return {
7✔
323
                valid: false,
324
                message: "user.passwordMinlenght",
325
                args: this.props.minPasswordSize
326
            };
327
        }
328

329
        return validation;
15✔
330
    };
331

332
    isSaving = () => {
19✔
333
        return this.props.user && this.props.user.status === "saving";
27✔
334
    };
335

336
    isSaved = () => {
19✔
337
        return this.props.user && (this.props.user.status === "saved" || this.props.user.status === "created");
26✔
338
    };
339

340
    isValid = () => {
19✔
341
        let user = this.props.user;
26✔
342
        return user && user.name && user.status === "modified" && this.isValidPassword();
26✔
343
    };
344

345
    isValidPassword = () => {
19✔
346
        let user = this.props.user;
30✔
347
        return user && this.isMainPasswordValid(user.newPassword).valid && (user.confirmPassword === user.newPassword);
30✔
348
    };
349

350
    handleChange = (event) => {
19✔
351
        this.props.onChange(event.target.name, event.target.value);
×
352
        // this.setState(() => ({[event.target.name]: event.target.value}));
353
    };
354
}
355

356
export default UserDialog;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc