Line data Source code
1 : /*
2 : * Copyright (C) 2023 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 19 : import cockpit from 'cockpit';
7 19 : import React from 'react';
8 :
9 : import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
10 : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
11 : import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog.jsx";
12 : import { FormHelper } from "cockpit-components-form-helper";
13 :
14 : import { has_errors, is_valid_char_name } from "./dialog-utils.js";
15 :
16 19 : const _ = cockpit.gettext;
17 :
18 1 : function GroupCreateBody({ state, errors, change }) {
19 1 : const {
20 1 : name, id,
21 1 : } = state;
22 :
23 1 : return (
24 1 : <Form isHorizontal onSubmit={apply_modal_dialog}>
25 1 : <FormGroup label={_("Name")}
26 1 : fieldId="groups-create-name">
27 1 : <TextInput id="groups-create-name"
28 1 : validated={(errors?.name) ? "error" : "default"}
29 1 : value={name} onChange={(_event, value) => change("name", value)} />
30 1 : <FormHelper fieldId="groups-create-name" helperTextInvalid={errors?.name} />
31 1 : </FormGroup>
32 :
33 1 : <FormGroup label={_("ID")}
34 1 : hasNoPaddingTop
35 1 : isStack
36 1 : fieldId="groups-create-id">
37 1 : <TextInput id="groups-create-id"
38 1 : validated={(errors?.id) ? "error" : "default"}
39 1 : value={id} onChange={(_event, value) => change("id", value)} />
40 1 : <FormHelper fieldId="groups-create-id" helperTextInvalid={errors?.id} />
41 1 : </FormGroup>
42 1 : </Form>
43 : );
44 1 : }
45 :
46 1 : function validate_name(name, groups) {
47 1 : if (!name)
48 1 : return _("No group name specified");
49 :
50 1 : for (let i = 0; i < name.length; i++) {
51 1 : if (!is_valid_char_name(name[i]))
52 1 : return _("Group name can only consist of letters, digits, dots, dashes, and underscores");
53 1 : }
54 :
55 1 : for (let k = 0; k < groups.length; k++) {
56 1 : if (groups[k].name == name)
57 1 : return _("A group with this name already exists");
58 1 : }
59 :
60 1 : return null;
61 1 : }
62 :
63 1 : function validate_group(id, groups) {
64 1 : if (!id)
65 1 : return _("No ID specified");
66 :
67 1 : const id_num = parseInt(id);
68 1 : if (id_num.toString() !== id || id_num < 0)
69 1 : return _("Group ID must be positive integer");
70 :
71 1 : return null;
72 1 : }
73 :
74 1 : export function group_create_dialog(groups, setGroupsCardExpanded, min_gid, max_gid) {
75 1 : let dlg = null;
76 1 : const state = {
77 1 : name: "",
78 1 : id: "",
79 1 : };
80 1 : let errors = { };
81 :
82 1 : const gids = groups
83 1 : .filter(g => g.name !== 'nobody')
84 1 : .map(group => group.gid);
85 :
86 1 : change("id", (Math.max(min_gid, Math.max(...gids.filter(id => id < max_gid)) + 1) + 1).toString());
87 :
88 1 : function change(field, value) {
89 1 : state[field] = value;
90 1 : errors = { };
91 :
92 1 : update();
93 1 : }
94 :
95 1 : function validate(name, id) {
96 1 : const errs = { };
97 :
98 1 : errs.name = validate_name(name, groups);
99 1 : errs.id = validate_group(id, groups);
100 1 : errors = errs;
101 :
102 1 : return !has_errors(errs);
103 1 : }
104 :
105 1 : function create(name, id) {
106 1 : const valid = validate(name, id);
107 1 : if (valid) {
108 1 : const group_add_cmd = ["groupadd", name, "-g", id];
109 :
110 1 : return cockpit.spawn(group_add_cmd, { superuser: "require", err: "message" });
111 1 : } else {
112 1 : update();
113 1 : return Promise.reject();
114 1 : }
115 1 : }
116 :
117 1 : function update() {
118 1 : const props = {
119 1 : id: "groups-create-dialog",
120 1 : title: _("Create new group"),
121 1 : body: <GroupCreateBody state={state} errors={errors} change={change} />
122 1 : };
123 :
124 1 : const footer = {
125 1 : actions: [
126 1 : {
127 1 : caption: _("Create"),
128 1 : style: "primary",
129 1 : clicked: () => create(state.name, state.id).then(() => setGroupsCardExpanded(true))
130 1 : }
131 1 : ]
132 1 : };
133 :
134 1 : if (!dlg)
135 1 : dlg = show_modal_dialog(props, footer);
136 1 : else {
137 1 : dlg.setProps(props);
138 1 : dlg.setFooterProps(footer);
139 1 : }
140 1 : }
141 :
142 1 : update();
143 1 : }
|