mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
support for managing external managed groups
This commit is contained in:
@@ -50,6 +50,7 @@ import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -195,6 +196,7 @@ public class Group extends BasicPropertiesAware
|
||||
group.setMembers(members);
|
||||
group.setType(type);
|
||||
group.setDescription(description);
|
||||
group.setExternal(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,6 +226,7 @@ public class Group extends BasicPropertiesAware
|
||||
&& Objects.equal(description, other.description)
|
||||
&& Objects.equal(members, other.members)
|
||||
&& Objects.equal(type, other.type)
|
||||
&& Objects.equal(external, other.external)
|
||||
&& Objects.equal(creationDate, other.creationDate)
|
||||
&& Objects.equal(lastModified, other.lastModified)
|
||||
&& Objects.equal(properties, other.properties);
|
||||
@@ -270,6 +273,7 @@ public class Group extends BasicPropertiesAware
|
||||
.add("description", description)
|
||||
.add("members", members)
|
||||
.add("type", type)
|
||||
.add("external", external)
|
||||
.add("creationDate", creationDate)
|
||||
.add("lastModified", lastModified)
|
||||
.add("properties", properties)
|
||||
@@ -339,8 +343,9 @@ public class Group extends BasicPropertiesAware
|
||||
*/
|
||||
public List<String> getMembers()
|
||||
{
|
||||
if (members == null)
|
||||
{
|
||||
if (external) {
|
||||
return Collections.emptyList();
|
||||
} else if (members == null) {
|
||||
members = Lists.newArrayList();
|
||||
}
|
||||
|
||||
@@ -370,6 +375,15 @@ public class Group extends BasicPropertiesAware
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the members of the groups managed external of scm-manager.
|
||||
*
|
||||
* @return {@code true} if the group is an external group
|
||||
*/
|
||||
public boolean isExternal() {
|
||||
return external;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the member is a member of this group.
|
||||
*
|
||||
@@ -463,8 +477,21 @@ public class Group extends BasicPropertiesAware
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code true} to mark the group as external.
|
||||
*
|
||||
* @param {@code true} for a external group
|
||||
*/
|
||||
public void setExternal(boolean external)
|
||||
{
|
||||
this.external = external;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** external group */
|
||||
private boolean external = false;
|
||||
|
||||
/** timestamp of the creation date of this group */
|
||||
private Long creationDate;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export type Group = Collection & {
|
||||
name: string,
|
||||
description: string,
|
||||
type: string,
|
||||
external: boolean,
|
||||
members: string[],
|
||||
_embedded: {
|
||||
members: Member[]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"creationDate": "Erstellt",
|
||||
"lastModified": "Zuletzt bearbeitet",
|
||||
"type": "Typ",
|
||||
"external": "Extern",
|
||||
"members": "Mitglieder"
|
||||
},
|
||||
"groups": {
|
||||
@@ -49,13 +50,15 @@
|
||||
},
|
||||
"groupForm": {
|
||||
"subtitle": "Gruppe bearbeiten",
|
||||
"externalSubtitle": "Externe Gruppe bearbeiten",
|
||||
"submit": "Speichern",
|
||||
"nameError": "Name ist ungültig",
|
||||
"descriptionError": "Beschreibung ist ungültig",
|
||||
"help": {
|
||||
"nameHelpText": "Eindeutiger Name der Gruppe",
|
||||
"descriptionHelpText": "Eine kurze Beschreibung der Gruppe",
|
||||
"memberHelpText": "Benutzername des Mitglieds der Gruppe"
|
||||
"memberHelpText": "Benutzername des Mitglieds der Gruppe",
|
||||
"externalHelpText": "Mitglieder dieser Gruppe werden von einem externen System wie z.B.: einem LDAP-Server verwaltet"
|
||||
}
|
||||
},
|
||||
"deleteGroup": {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"creationDate": "Creation Date",
|
||||
"lastModified": "Last Modified",
|
||||
"type": "Type",
|
||||
"external": "External",
|
||||
"members": "Members"
|
||||
},
|
||||
"groups": {
|
||||
@@ -49,13 +50,15 @@
|
||||
},
|
||||
"groupForm": {
|
||||
"subtitle": "Edit Group",
|
||||
"externalSubtitle": "Edit external group",
|
||||
"submit": "Submit",
|
||||
"nameError": "Group name is invalid",
|
||||
"descriptionError": "Description is invalid",
|
||||
"help": {
|
||||
"nameHelpText": "Unique name of the group",
|
||||
"descriptionHelpText": "A short description of the group",
|
||||
"memberHelpText": "Usernames of the group members"
|
||||
"memberHelpText": "Usernames of the group members",
|
||||
"externalHelpText": "Members are managed by an external system such as LDAP"
|
||||
}
|
||||
},
|
||||
"deleteGroup": {
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
MemberNameTable,
|
||||
InputField,
|
||||
SubmitButton,
|
||||
Textarea
|
||||
Textarea,
|
||||
Checkbox
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { Group, SelectValue } from "@scm-manager/ui-types";
|
||||
|
||||
@@ -67,16 +68,68 @@ class GroupForm extends React.Component<Props, State> {
|
||||
submit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (this.isValid()) {
|
||||
this.props.submitForm(this.state.group);
|
||||
const { group } = this.state;
|
||||
if (group.external) {
|
||||
group.members = [];
|
||||
}
|
||||
this.props.submitForm(group);
|
||||
}
|
||||
};
|
||||
|
||||
renderMemberfields = (group: Group) => {
|
||||
if (group.external) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { loadUserSuggestions, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
<LabelWithHelpIcon
|
||||
label={t("group.members")}
|
||||
helpText={t("groupForm.help.memberHelpText")}
|
||||
/>
|
||||
<MemberNameTable
|
||||
members={group.members}
|
||||
memberListChanged={this.memberListChanged}
|
||||
/>
|
||||
<AutocompleteAddEntryToTableField
|
||||
addEntry={this.addMember}
|
||||
disabled={false}
|
||||
buttonLabel={t("add-member-button.label")}
|
||||
fieldLabel={t("add-member-textfield.label")}
|
||||
errorMessage={t("add-member-textfield.error")}
|
||||
loadSuggestions={loadUserSuggestions}
|
||||
placeholder={t("add-member-autocomplete.placeholder")}
|
||||
loadingMessage={t("add-member-autocomplete.loading")}
|
||||
noOptionsMessage={t("add-member-autocomplete.no-options")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderExternalField = (group: Group) => {
|
||||
const { t } = this.props;
|
||||
if (this.isExistingGroup()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Checkbox
|
||||
label={t("group.external")}
|
||||
checked={group.external}
|
||||
helpText={t("groupForm.help.externalHelpText")}
|
||||
onChange={this.handleExternalChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
isExistingGroup = () => !! this.props.group;
|
||||
|
||||
render() {
|
||||
const { loading, t } = this.props;
|
||||
const { group } = this.state;
|
||||
let nameField = null;
|
||||
let subtitle = null;
|
||||
if (!this.props.group) {
|
||||
if (!this.isExistingGroup()) {
|
||||
// create new group
|
||||
nameField = (
|
||||
<InputField
|
||||
@@ -88,8 +141,9 @@ class GroupForm extends React.Component<Props, State> {
|
||||
helpText={t("groupForm.help.nameHelpText")}
|
||||
/>
|
||||
);
|
||||
} else if (group.external) {
|
||||
subtitle = <Subtitle subtitle={t("groupForm.externalSubtitle")} />;
|
||||
} else {
|
||||
// edit existing group
|
||||
subtitle = <Subtitle subtitle={t("groupForm.subtitle")} />;
|
||||
}
|
||||
|
||||
@@ -106,26 +160,8 @@ class GroupForm extends React.Component<Props, State> {
|
||||
validationError={false}
|
||||
helpText={t("groupForm.help.descriptionHelpText")}
|
||||
/>
|
||||
<LabelWithHelpIcon
|
||||
label={t("group.members")}
|
||||
helpText={t("groupForm.help.memberHelpText")}
|
||||
/>
|
||||
<MemberNameTable
|
||||
members={group.members}
|
||||
memberListChanged={this.memberListChanged}
|
||||
/>
|
||||
|
||||
<AutocompleteAddEntryToTableField
|
||||
addEntry={this.addMember}
|
||||
disabled={false}
|
||||
buttonLabel={t("add-member-button.label")}
|
||||
fieldLabel={t("add-member-textfield.label")}
|
||||
errorMessage={t("add-member-textfield.error")}
|
||||
loadSuggestions={this.props.loadUserSuggestions}
|
||||
placeholder={t("add-member-autocomplete.placeholder")}
|
||||
loadingMessage={t("add-member-autocomplete.loading")}
|
||||
noOptionsMessage={t("add-member-autocomplete.no-options")}
|
||||
/>
|
||||
{this.renderExternalField(group)}
|
||||
{this.renderMemberfields(group)}
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
label={t("groupForm.submit")}
|
||||
@@ -176,6 +212,12 @@ class GroupForm extends React.Component<Props, State> {
|
||||
group: { ...this.state.group, description }
|
||||
});
|
||||
};
|
||||
|
||||
handleExternalChange = (external: boolean) => {
|
||||
this.setState({
|
||||
group: { ...this.state.group, external }
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("groups")(GroupForm);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from "react";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import GroupMember from "./GroupMember";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import { DateFromNow, Checkbox } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import injectSheet from "react-jss";
|
||||
|
||||
@@ -34,6 +34,12 @@ class Details extends React.Component<Props> {
|
||||
<th>{t("group.description")}</th>
|
||||
<td>{group.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("group.external")}</th>
|
||||
<td>
|
||||
<Checkbox checked={group.external} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("group.type")}</th>
|
||||
<td>{group.type}</td>
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
group: Group
|
||||
};
|
||||
|
||||
export default class GroupRow extends React.Component<Props> {
|
||||
renderLink(to: string, label: string) {
|
||||
return <Link to={to}>{label}</Link>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const to = `/group/${group.name}`;
|
||||
return (
|
||||
<tr>
|
||||
<td>{this.renderLink(to, group.name)}</td>
|
||||
<td className="is-hidden-mobile">{group.description}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { Checkbox } from "@scm-manager/ui-components"
|
||||
|
||||
type Props = {
|
||||
group: Group
|
||||
};
|
||||
|
||||
export default class GroupRow extends React.Component<Props> {
|
||||
renderLink(to: string, label: string) {
|
||||
return <Link to={to}>{label}</Link>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const to = `/group/${group.name}`;
|
||||
return (
|
||||
<tr>
|
||||
<td>{this.renderLink(to, group.name)}</td>
|
||||
<td className="is-hidden-mobile">{group.description}</td>
|
||||
<td>
|
||||
<Checkbox checked={group.external} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import GroupRow from "./GroupRow";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
groups: Group[]
|
||||
};
|
||||
|
||||
class GroupTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { groups, t } = this.props;
|
||||
return (
|
||||
<table className="card-table table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("group.name")}</th>
|
||||
<th className="is-hidden-mobile">{t("group.description")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{groups.map((group, index) => {
|
||||
return <GroupRow key={index} group={group} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(GroupTable);
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import GroupRow from "./GroupRow";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
groups: Group[]
|
||||
};
|
||||
|
||||
class GroupTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { groups, t } = this.props;
|
||||
return (
|
||||
<table className="card-table table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("group.name")}</th>
|
||||
<th className="is-hidden-mobile">{t("group.description")}</th>
|
||||
<th>{t("group.external")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{groups.map((group, index) => {
|
||||
return <GroupRow key={index} group={group} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(GroupTable);
|
||||
|
||||
@@ -27,6 +27,7 @@ public class GroupDto extends HalRepresentation {
|
||||
private String type;
|
||||
private Map<String, String> properties;
|
||||
private List<String> members;
|
||||
private boolean external;
|
||||
|
||||
GroupDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
|
||||
Reference in New Issue
Block a user