Merge with custom roles branch

This commit is contained in:
René Pfeuffer
2019-05-16 14:31:56 +02:00
35 changed files with 759 additions and 381 deletions

View File

@@ -12,6 +12,8 @@ export type ButtonProps = {
fullWidth?: boolean, fullWidth?: boolean,
className?: string, className?: string,
children?: React.Node, children?: React.Node,
// context props
classes: any classes: any
}; };

View File

@@ -1,14 +1,15 @@
//@flow //@flow
import type {Links} from "./hal"; import type {Links} from "./hal";
export type PermissionCreateEntry = {
name: string,
role?: string,
verbs?: string[],
groupPermission: boolean
}
export type Permission = PermissionCreateEntry & { export type Permission = PermissionCreateEntry & {
_links: Links _links: Links
}; };
export type PermissionCreateEntry = {
name: string,
verbs: string[],
groupPermission: boolean
}
export type PermissionCollection = Permission[]; export type PermissionCollection = Permission[];

View File

@@ -1,10 +1,13 @@
// @flow // @flow
import type {Links} from "./hal";
export type RepositoryRole = { export type RepositoryRole = {
name: string, name: string,
verbs: string[], verbs: string[],
type?: string, type?: string,
creationDate?: string, creationDate?: string,
lastModified?: string, lastModified?: string,
_links: Links
}; };

View File

@@ -1,12 +0,0 @@
//@flow
import type { Links } from "./hal";
export type Role = {
name: string,
verbs: string[],
creationDate?: number,
lastModified?: number,
system: boolean,
_links: Links
};

View File

@@ -16,7 +16,6 @@ export type { Changeset } from "./Changesets";
export type { Tag } from "./Tags"; export type { Tag } from "./Tags";
export type { Config } from "./Config"; export type { Config } from "./Config";
export type { Role } from "./Role";
export type { IndexResources } from "./IndexResources"; export type { IndexResources } from "./IndexResources";

View File

@@ -6,12 +6,22 @@
"errorTitle": "Fehler", "errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Einstellungen Fehler" "errorSubtitle": "Unbekannter Einstellungen Fehler"
}, },
"roles": { "repositoryRole": {
"navLink": "Berechtigungsrollen", "navLink": "Berechtigungsrollen",
"title": "Berechtigungsrollen", "title": "Berechtigungsrollen",
"noPermissionRoles": "Keine Berechtigungsrollen gefunden.", "noPermissionRoles": "Keine Berechtigungsrollen gefunden.",
"system": "System", "system": "System",
"createButton": "Berechtigungsrolle erstellen", "createButton": "Berechtigungsrolle erstellen",
"name": "Name",
"type": "Typ",
"verbs": "Verben",
"button": {
"edit": "Bearbeiten"
},
"create": {
"name": "Name"
},
"edit": "Berechtigungsrolle bearbeiten",
"form": { "form": {
"subtitle": "Berechtigungsrolle bearbeiten", "subtitle": "Berechtigungsrolle bearbeiten",
"name": "Name", "name": "Name",
@@ -19,6 +29,10 @@
"submit": "Speichern" "submit": "Speichern"
} }
}, },
"role": {
"name": "Name",
"system": "System"
},
"config-form": { "config-form": {
"submit": "Speichern", "submit": "Speichern",
"submit-success-notification": "Einstellungen wurden erfolgreich geändert!", "submit-success-notification": "Einstellungen wurden erfolgreich geändert!",

View File

@@ -119,6 +119,7 @@
"error-subtitle": "Unbekannter Fehler bei Berechtigung", "error-subtitle": "Unbekannter Fehler bei Berechtigung",
"name": "Benutzer oder Gruppe", "name": "Benutzer oder Gruppe",
"role": "Rolle", "role": "Rolle",
"custom": "CUSTOM",
"permissions": "Berechtigung", "permissions": "Berechtigung",
"group-permission": "Gruppenberechtigung", "group-permission": "Gruppenberechtigung",
"user-permission": "Benutzerberechtigung", "user-permission": "Benutzerberechtigung",

View File

@@ -6,11 +6,22 @@
"errorTitle": "Error", "errorTitle": "Error",
"errorSubtitle": "Unknown Config Error" "errorSubtitle": "Unknown Config Error"
}, },
"roles": { "repositoryRole": {
"navLink": "Permission Roles", "navLink": "Permission Roles",
"title": "Permission Roles", "title": "Permission Roles",
"noPermissionRoles": "No permission roles found.", "noPermissionRoles": "No permission roles found.",
"system": "System",
"createButton": "Create Permission Role", "createButton": "Create Permission Role",
"name": "Name",
"type": "Type",
"verbs": "Verbs",
"edit": "Edit Permission Role",
"button": {
"edit": "Edit"
},
"create": {
"name": "Name"
},
"form": { "form": {
"subtitle": "Edit Permission Role", "subtitle": "Edit Permission Role",
"name": "Name", "name": "Name",

View File

@@ -122,6 +122,7 @@
"error-subtitle": "Unknown permissions error", "error-subtitle": "Unknown permissions error",
"name": "User or group", "name": "User or group",
"role": "Role", "role": "Role",
"custom": "CUSTOM",
"permissions": "Permissions", "permissions": "Permissions",
"group-permission": "Group Permission", "group-permission": "Group Permission",
"user-permission": "User Permission", "user-permission": "User Permission",

View File

@@ -1,7 +1,7 @@
// @flow // @flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { Route } from "react-router"; import { Route, Switch } from "react-router-dom";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { History } from "history"; import type { History } from "history";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -10,9 +10,9 @@ import type { Links } from "@scm-manager/ui-types";
import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components";
import { getLinks } from "../../modules/indexResource"; import { getLinks } from "../../modules/indexResource";
import GlobalConfig from "./GlobalConfig"; import GlobalConfig from "./GlobalConfig";
import PermissionRolesOverview from "../roles/containers/PermissionRolesOverview"; import RepositoryRoles from "../roles/containers/RepositoryRoles";
import PermissionRoleRoot from "../roles/containers/PermissionRoleRoot"; import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
import CreatePermissionRole from "../roles/containers/CreatePermissionRole"; import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
type Props = { type Props = {
links: Links, links: Links,
@@ -37,7 +37,7 @@ class Config extends React.Component<Props> {
matchesRoles = (route: any) => { matchesRoles = (route: any) => {
const url = this.matchedUrl(); const url = this.matchedUrl();
const regex = new RegExp(`${url}/role/.+/info`); const regex = new RegExp(`${url}/role/`);
return route.location.pathname.match(regex); return route.location.pathname.match(regex);
}; };
@@ -54,25 +54,44 @@ class Config extends React.Component<Props> {
<Page> <Page>
<div className="columns"> <div className="columns">
<div className="column is-three-quarters"> <div className="column is-three-quarters">
<Switch>
<Route path={url} exact component={GlobalConfig} /> <Route path={url} exact component={GlobalConfig} />
<Route <Route
path={`${url}/role/:role`} path={`${url}/role/:role`}
component={() => <PermissionRoleRoot baseUrl={`${url}/roles`} />} render={() => (
<SingleRepositoryRole
baseUrl={`${url}/roles`}
history={this.props.history}
/>
)}
/> />
<Route <Route
path={`${url}/roles`} path={`${url}/roles`}
exact exact
render={() => <PermissionRolesOverview baseUrl={`${url}/role`} />} render={() => <RepositoryRoles baseUrl={`${url}/roles`} />}
/> />
<Route <Route
path={`${url}/roles/create`} path={`${url}/roles/create`}
render={() => <CreatePermissionRole />} render={() => (
<CreateRepositoryRole
disabled={false}
history={this.props.history}
/>
)}
/>
<Route
path={`${url}/roles/:page`}
exact
render={() => (
<RepositoryRoles baseUrl={`${url}/roles`} />
)}
/> />
<ExtensionPoint <ExtensionPoint
name="config.route" name="config.route"
props={extensionProps} props={extensionProps}
renderAll={true} renderAll={true}
/> />
</Switch>
</div> </div>
<div className="column is-one-quarter"> <div className="column is-one-quarter">
<Navigation> <Navigation>
@@ -83,9 +102,8 @@ class Config extends React.Component<Props> {
/> />
<NavLink <NavLink
to={`${url}/roles/`} to={`${url}/roles/`}
label={t("roles.navLink")} label={t("repositoryRole.navLink")}
activeWhenMatch={this.matchesRoles} activeWhenMatch={this.matchesRoles}
activeOnlyWhenExact={false}
/> />
<ExtensionPoint <ExtensionPoint
name="config.navigation" name="config.navigation"

View File

@@ -0,0 +1,47 @@
//@flow
import React from "react";
import type { RepositoryRole } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import { compose } from "redux";
import injectSheet from "react-jss";
type Props = {
role: RepositoryRole,
// context props
t: string => string
};
const styles = {
spacing: {
padding: "0 !important"
}
};
class AvailableVerbs extends React.Component<Props> {
render() {
const { role, t, classes } = this.props;
let verbs = null;
if (role.verbs.length > 0) {
verbs = (
<tr>
<td className={classes.spacing}>
<ul>
{role.verbs.map(verb => {
return (
<li>{t("verbs.repository." + verb + ".displayName")}</li>
);
})}
</ul>
</td>
</tr>
);
}
return verbs;
}
}
export default compose(
injectSheet(styles),
translate("plugins")
)(AvailableVerbs);

View File

@@ -1,29 +0,0 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import type { Role } from "@scm-manager/ui-types";
import SystemRoleTag from "./SystemRoleTag";
type Props = {
role: Role,
// context props
t: string => string,
};
class PermissionRoleDetail extends React.Component<Props> {
render() {
const { role, t } = this.props;
return (
<div className="media">
<div className="media-content subtitle">
<strong>{t("role.name")}:</strong> {role.name}{" "}
<SystemRoleTag system={!role._links.update} />
</div>
</div>
);
}
}
export default translate("config")(PermissionRoleDetail);

View File

@@ -0,0 +1,52 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import type { RepositoryRole } from "@scm-manager/ui-types";
import ExtensionPoint from "@scm-manager/ui-extensions/lib/ExtensionPoint";
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
import { Button, Subtitle } from "@scm-manager/ui-components";
type Props = {
role: RepositoryRole,
url: string,
// context props
t: string => string
};
class PermissionRoleDetails extends React.Component<Props> {
renderEditButton() {
const { t, url } = this.props;
if (!!this.props.role._links.update) {
return (
<Button
label={t("repositoryRole.button.edit")}
link={`${url}/edit`}
color="primary"
/>
);
}
return null;
}
render() {
const { role } = this.props;
return (
<div>
<PermissionRoleDetailsTable role={role} />
<hr />
{this.renderEditButton()}
<div className="content">
<ExtensionPoint
name="repositoryRole.role-details.information"
renderAll={true}
props={{ role }}
/>
</div>
</div>
);
}
}
export default translate("config")(PermissionRoleDetails);

View File

@@ -0,0 +1,37 @@
//@flow
import React from "react";
import type { RepositoryRole } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import AvailableVerbs from "./AvailableVerbs";
type Props = {
role: RepositoryRole,
// context props
t: string => string
};
class PermissionRoleDetailsTable extends React.Component<Props> {
render() {
const { role, t } = this.props;
return (
<table className="table content">
<tbody>
<tr>
<th>{t("repositoryRole.name")}</th>
<td>{role.name}</td>
</tr>
<tr>
<th>{t("repositoryRole.type")}</th>
<td>{role.type}</td>
</tr>
<tr>
<th>{t("repositoryRole.verbs")}</th>
<AvailableVerbs role={role} />
</tr>
</tbody>
</table>
);
}
}
export default translate("config")(PermissionRoleDetailsTable);

View File

@@ -1,12 +1,12 @@
// @flow // @flow
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import type { Role } from "@scm-manager/ui-types"; import type { RepositoryRole } from "@scm-manager/ui-types";
import SystemRoleTag from "./SystemRoleTag"; import SystemRoleTag from "./SystemRoleTag";
type Props = { type Props = {
baseUrl: string, baseUrl: string,
role: Role role: RepositoryRole
}; };
class PermissionRoleRow extends React.Component<Props> { class PermissionRoleRow extends React.Component<Props> {
@@ -20,7 +20,8 @@ class PermissionRoleRow extends React.Component<Props> {
render() { render() {
const { baseUrl, role } = this.props; const { baseUrl, role } = this.props;
const to = `${baseUrl}/${encodeURIComponent(role.name)}/info`; const singleRepoRoleUrl = baseUrl.substring(0, baseUrl.length - 1);
const to = `${singleRepoRoleUrl}/${encodeURIComponent(role.name)}/info`;
return ( return (
<tr> <tr>
<td>{this.renderLink(to, role.name, !role._links.update)}</td> <td>{this.renderLink(to, role.name, !role._links.update)}</td>

View File

@@ -1,12 +1,12 @@
// @flow // @flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type { Role } from "@scm-manager/ui-types"; import type { RepositoryRole } from "@scm-manager/ui-types";
import PermissionRoleRow from "./PermissionRoleRow"; import PermissionRoleRow from "./PermissionRoleRow";
type Props = { type Props = {
baseUrl: string, baseUrl: string,
roles: Role[], roles: RepositoryRole[],
t: string => string t: string => string
}; };
@@ -18,7 +18,7 @@ class PermissionRoleTable extends React.Component<Props> {
<table className="card-table table is-hoverable is-fullwidth"> <table className="card-table table is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
<th>{t("roles.form.name")}</th> <th>{t("repositoryRole.form.name")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -1,12 +0,0 @@
// @flow
import React from "react";
type Props = {};
class CreatePermissionRole extends React.Component<Props> {
render() {
return <>yep</>;
}
}
export default CreatePermissionRole;

View File

@@ -0,0 +1,87 @@
// @flow
import React from "react";
import RepositoryRoleForm from "./RepositoryRoleForm";
import { connect } from "react-redux";
import { translate } from "react-i18next";
import { ErrorNotification, Title } from "@scm-manager/ui-components";
import {
createRole,
getCreateRoleFailure,
getFetchVerbsFailure,
isFetchVerbsPending
} from "../modules/roles";
import type { RepositoryRole } from "@scm-manager/ui-types";
import {
getRepositoryRolesLink,
getRepositoryVerbsLink
} from "../../../modules/indexResource";
type Props = {
disabled: boolean,
repositoryRolesLink: string,
error?: Error,
//dispatch function
addRole: (link: string, role: RepositoryRole, callback?: () => void) => void,
// context objects
t: string => string
};
class CreateRepositoryRole extends React.Component<Props> {
repositoryRoleCreated = (role: RepositoryRole) => {
const { history } = this.props;
history.push("/config/role/" + role.name + "/info");
};
createRepositoryRole = (role: RepositoryRole) => {
this.props.addRole(this.props.repositoryRolesLink, role, () =>
this.repositoryRoleCreated(role)
);
};
render() {
const { t, error } = this.props;
if (error) {
return <ErrorNotification error={error} />;
}
return (
<>
<Title title={t("repositoryRole.title")} />
<RepositoryRoleForm
disabled={this.props.disabled}
submitForm={role => this.createRepositoryRole(role)}
/>
</>
);
}
}
const mapStateToProps = (state, ownProps) => {
const loading = isFetchVerbsPending(state);
const error = getFetchVerbsFailure(state) || getCreateRoleFailure(state);
const verbsLink = getRepositoryVerbsLink(state);
const repositoryRolesLink = getRepositoryRolesLink(state);
return {
loading,
error,
verbsLink,
repositoryRolesLink
};
};
const mapDispatchToProps = dispatch => {
return {
addRole: (link: string, role: RepositoryRole, callback?: () => void) => {
dispatch(createRole(link, role, callback));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(translate("config")(CreateRepositoryRole));

View File

@@ -0,0 +1,78 @@
// @flow
import React from "react";
import RepositoryRoleForm from "./RepositoryRoleForm";
import { connect } from "react-redux";
import { translate } from "react-i18next";
import {
getModifyRoleFailure,
isModifyRolePending,
modifyRole
} from "../modules/roles";
import { ErrorNotification } from "@scm-manager/ui-components";
import type { RepositoryRole } from "@scm-manager/ui-types";
type Props = {
disabled: boolean,
role: RepositoryRole,
repositoryRolesLink: string,
error?: Error,
//dispatch function
updateRole: (
link: string,
role: RepositoryRole,
callback?: () => void
) => void
};
class EditRepositoryRole extends React.Component<Props> {
repositoryRoleUpdated = (role: RepositoryRole) => {
const { history } = this.props;
history.push("/config/roles/");
};
updateRepositoryRole = (role: RepositoryRole) => {
this.props.updateRole(role, () => this.repositoryRoleUpdated(role));
};
render() {
const { error } = this.props;
if (error) {
return <ErrorNotification error={error} />;
}
return (
<>
<RepositoryRoleForm
nameDisabled={true}
role={this.props.role}
submitForm={role => this.updateRepositoryRole(role)}
/>
</>
);
}
}
const mapStateToProps = (state, ownProps) => {
const loading = isModifyRolePending(state);
const error = getModifyRoleFailure(state, ownProps.role.name);
return {
loading,
error
};
};
const mapDispatchToProps = dispatch => {
return {
updateRole: (role: RepositoryRole, callback?: () => void) => {
dispatch(modifyRole(role, callback));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(translate("config")(EditRepositoryRole));

View File

@@ -1,103 +0,0 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import { Redirect, Route, Switch, withRouter } from "react-router-dom";
import type {Role} from "@scm-manager/ui-types";
import {ErrorNotification, Loading} from "@scm-manager/ui-components";
import { getRolesLink } from "../../../modules/indexResource";
import {
fetchRoleByName,
getRoleByName,
isFetchRolePending,
getFetchRoleFailure
} from "../modules/roles";
import PermissionRoleDetail from "../components/PermissionRoleDetail";
type Props = {
roleLink: string,
roleName: string,
role: Role,
loading: boolean,
error: Error,
// context props
match: any,
t: string => string,
// dispatch functions
fetchRoleByName: (roleLink: string, roleName: string) => void
};
class PermissionRoleRoot extends React.Component<Props> {
componentDidMount() {
const { fetchRoleByName, roleLink, roleName } = this.props;
fetchRoleByName(roleLink, roleName);
}
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 1);
}
return url;
};
matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url);
};
render() {
const { loading, error, role} = this.props;
const url = this.matchedUrl();
if (error) {
return <ErrorNotification error={error} />;
}
if (loading || !role) {
return <Loading />;
}
return (
<Switch>
<Redirect exact from={url} to={`${url}/info`} />
<Route
path={`${url}/info`}
component={() => (
<PermissionRoleDetail role={role} />
)}
/>
</Switch>
);
}
}
const mapStateToProps = (state, ownProps) => {
const roleName = decodeURIComponent(ownProps.match.params.role);
const role = getRoleByName(state, roleName);
const loading = isFetchRolePending(state, roleName);
const error = getFetchRoleFailure(state, roleName);
const roleLink = getRolesLink(state);
return {
roleName,
role,
loading,
error,
roleLink
};
};
const mapDispatchToProps = dispatch => {
return {
fetchRoleByName: (roleLink: string, roleName: string) => {
dispatch(fetchRoleByName(roleLink, roleName));
}
};
};
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(PermissionRoleRoot)
);

View File

@@ -2,7 +2,7 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type { Role } from "@scm-manager/ui-types"; import type { RepositoryRole } from "@scm-manager/ui-types";
import { InputField, SubmitButton } from "@scm-manager/ui-components"; import { InputField, SubmitButton } from "@scm-manager/ui-components";
import PermissionCheckbox from "../../../repos/permissions/components/PermissionCheckbox"; import PermissionCheckbox from "../../../repos/permissions/components/PermissionCheckbox";
import { import {
@@ -11,14 +11,18 @@ import {
getVerbsFromState, getVerbsFromState,
isFetchVerbsPending isFetchVerbsPending
} from "../modules/roles"; } from "../modules/roles";
import { getRepositoryVerbsLink } from "../../../modules/indexResource"; import {
getRepositoryRolesLink,
getRepositoryVerbsLink
} from "../../../modules/indexResource";
type Props = { type Props = {
role?: Role, role?: RepositoryRole,
loading?: boolean, loading?: boolean,
disabled: boolean, nameDisabled: boolean,
availableVerbs: string[], availableVerbs: string[],
verbsLink: string, verbsLink: string,
submitForm: RepositoryRole => void,
// context objects // context objects
t: string => string, t: string => string,
@@ -28,10 +32,10 @@ type Props = {
}; };
type State = { type State = {
role: Role role: RepositoryRole
}; };
class GlobalPermissionRoleForm extends React.Component<Props, State> { class RepositoryRoleForm extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@@ -46,16 +50,10 @@ class GlobalPermissionRoleForm extends React.Component<Props, State> {
} }
componentDidMount() { componentDidMount() {
const { fetchAvailableVerbs, verbsLink, role } = this.props; const { fetchAvailableVerbs, verbsLink } = this.props;
fetchAvailableVerbs(verbsLink); fetchAvailableVerbs(verbsLink);
if (this.props.role) {
if (role) { this.setState({ role: this.props.role });
this.setState({
role: {
...role,
role: { verbs: role.verbs }
}
});
} }
} }
@@ -68,7 +66,7 @@ class GlobalPermissionRoleForm extends React.Component<Props, State> {
return !( return !(
this.isFalsy(role) || this.isFalsy(role) ||
this.isFalsy(role.name) || this.isFalsy(role.name) ||
this.isFalsy(role.verbs) this.isFalsy(role.verbs.length > 0)
); );
}; };
@@ -100,13 +98,12 @@ class GlobalPermissionRoleForm extends React.Component<Props, State> {
submit = (event: Event) => { submit = (event: Event) => {
event.preventDefault(); event.preventDefault();
if (this.isValid()) { if (this.isValid()) {
// this.props.submitForm(this.state.role); this.props.submitForm(this.state.role);
//TODO ADD createRole here
} }
}; };
render() { render() {
const { loading, availableVerbs, disabled, t } = this.props; const { loading, availableVerbs, nameDisabled, t } = this.props;
const { role } = this.state; const { role } = this.state;
const verbSelectBoxes = !availableVerbs const verbSelectBoxes = !availableVerbs
@@ -114,7 +111,6 @@ class GlobalPermissionRoleForm extends React.Component<Props, State> {
: availableVerbs.map(verb => ( : availableVerbs.map(verb => (
<PermissionCheckbox <PermissionCheckbox
key={verb} key={verb}
// disabled={readOnly}
name={verb} name={verb}
checked={role.verbs.includes(verb)} checked={role.verbs.includes(verb)}
onChange={this.handleVerbChange} onChange={this.handleVerbChange}
@@ -127,22 +123,21 @@ class GlobalPermissionRoleForm extends React.Component<Props, State> {
<div className="column"> <div className="column">
<InputField <InputField
name="name" name="name"
label={t("roles.form.name")} label={t("repositoryRole.create.name")}
onChange={this.handleNameChange} onChange={this.handleNameChange}
value={role.name ? role.name : ""} value={role.name ? role.name : ""}
disabled={!!role.name || disabled} disabled={nameDisabled}
/> />
</div> </div>
</div> </div>
<>{verbSelectBoxes}</>
{verbSelectBoxes}
<hr /> <hr />
<div className="columns"> <div className="columns">
<div className="column"> <div className="column">
<SubmitButton <SubmitButton
loading={loading} loading={loading}
label={t("roles.form.submit")} label={t("repositoryRole.form.submit")}
disabled={disabled || !this.isValid()} disabled={!this.isValid()}
/> />
</div> </div>
</div> </div>
@@ -156,12 +151,14 @@ const mapStateToProps = (state, ownProps) => {
const error = getFetchVerbsFailure(state); const error = getFetchVerbsFailure(state);
const verbsLink = getRepositoryVerbsLink(state); const verbsLink = getRepositoryVerbsLink(state);
const availableVerbs = getVerbsFromState(state); const availableVerbs = getVerbsFromState(state);
const repositoryRolesLink = getRepositoryRolesLink(state);
return { return {
loading, loading,
error, error,
verbsLink, verbsLink,
availableVerbs availableVerbs,
repositoryRolesLink
}; };
}; };
@@ -176,4 +173,4 @@ const mapDispatchToProps = dispatch => {
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(translate("config")(GlobalPermissionRoleForm)); )(translate("config")(RepositoryRoleForm));

View File

@@ -1,10 +1,10 @@
// @flow // @flow
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import {withRouter} from "react-router-dom"; import { withRouter } from "react-router-dom";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type { History } from "history"; import type { History } from "history";
import type { Role, PagedCollection } from "@scm-manager/ui-types"; import type { RepositoryRole, PagedCollection } from "@scm-manager/ui-types";
import { import {
Title, Title,
Loading, Loading,
@@ -23,10 +23,9 @@ import {
} from "../modules/roles"; } from "../modules/roles";
import PermissionRoleTable from "../components/PermissionRoleTable"; import PermissionRoleTable from "../components/PermissionRoleTable";
import { getRolesLink } from "../../../modules/indexResource"; import { getRolesLink } from "../../../modules/indexResource";
type Props = { type Props = {
baseUrl: string, baseUrl: string,
roles: Role[], roles: RepositoryRole[],
loading: boolean, loading: boolean,
error: Error, error: Error,
canAddRoles: boolean, canAddRoles: boolean,
@@ -42,12 +41,33 @@ type Props = {
fetchRolesByPage: (link: string, page: number) => void fetchRolesByPage: (link: string, page: number) => void
}; };
class PermissionRolesOverview extends React.Component<Props> { class RepositoryRoles extends React.Component<Props> {
componentDidMount() { componentDidMount() {
const { fetchRolesByPage, rolesLink, page } = this.props; const { fetchRolesByPage, rolesLink, page } = this.props;
fetchRolesByPage(rolesLink, page); fetchRolesByPage(rolesLink, page);
} }
componentDidUpdate = (prevProps: Props) => {
const {
loading,
list,
page,
rolesLink,
location,
fetchRolesByPage
} = this.props;
if (list && page && !loading) {
const statePage: number = list.page + 1;
if (page !== statePage || prevProps.location.search !== location.search) {
fetchRolesByPage(
rolesLink,
page,
urls.getQueryStringFromLocation(location)
);
}
}
};
render() { render() {
const { t, loading } = this.props; const { t, loading } = this.props;
@@ -57,7 +77,7 @@ class PermissionRolesOverview extends React.Component<Props> {
return ( return (
<div> <div>
<Title title={t("roles.title")} /> <Title title={t("repositoryRole.title")} />
{this.renderPermissionsTable()} {this.renderPermissionsTable()}
{this.renderCreateButton()} {this.renderCreateButton()}
</div> </div>
@@ -75,14 +95,21 @@ class PermissionRolesOverview extends React.Component<Props> {
); );
} }
return ( return (
<Notification type="info">{t("roles.noPermissionRoles")}</Notification> <Notification type="info">
{t("repositoryRole.noPermissionRoles")}
</Notification>
); );
} }
renderCreateButton() { renderCreateButton() {
const { canAddRoles, baseUrl, t } = this.props; const { canAddRoles, baseUrl, t } = this.props;
if (canAddRoles) { if (canAddRoles) {
return <CreateButton label={t("roles.createButton")} link={`${baseUrl}/create`} />; return (
<CreateButton
label={t("repositoryRole.createButton")}
link={`${baseUrl}/create`}
/>
);
} }
return null; return null;
} }
@@ -117,7 +144,9 @@ const mapDispatchToProps = dispatch => {
}; };
}; };
export default withRouter(connect( export default withRouter(
connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(translate("config")(PermissionRolesOverview))); )(translate("config")(RepositoryRoles))
);

View File

@@ -0,0 +1,137 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import { Loading, ErrorPage, Title } from "@scm-manager/ui-components";
import { Route } from "react-router";
import type { History } from "history";
import { translate } from "react-i18next";
import type { RepositoryRole } from "@scm-manager/ui-types";
import { getRepositoryRolesLink } from "../../../modules/indexResource";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import {
fetchRoleByName,
getFetchRoleFailure,
getRoleByName,
isFetchRolePending
} from "../modules/roles";
import { withRouter } from "react-router-dom";
import PermissionRoleDetail from "../components/PermissionRoleDetails";
import EditRepositoryRole from "./EditRepositoryRole";
type Props = {
roleName: string,
role: RepositoryRole,
loading: boolean,
error: Error,
repositoryRolesLink: string,
disabled: boolean,
// dispatcher function
fetchRoleByName: (string, string) => void,
// context objects
t: string => string,
match: any,
history: History
};
class SingleRepositoryRole extends React.Component<Props> {
componentDidMount() {
this.props.fetchRoleByName(
this.props.repositoryRolesLink,
this.props.roleName
);
}
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 2);
}
return url;
};
matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url);
};
render() {
const { t, loading, error, role } = this.props;
if (error) {
return (
<ErrorPage
title={t("repositoryRole.errorTitle")}
subtitle={t("repositoryRole.errorSubtitle")}
error={error}
/>
);
}
if (!role || loading) {
return <Loading />;
}
const url = this.matchedUrl();
const extensionProps = {
role,
url
};
return (
<>
<Title title={t("repositoryRole.title")} />
<div className="columns">
<div className="column is-three-quarters">
<Route
path={`${url}/info`}
component={() => <PermissionRoleDetail role={role} url={url} />}
/>
<Route
path={`${url}/edit`}
exact
component={() => (
<EditRepositoryRole role={role} history={this.props.history} />
)}
/>
<ExtensionPoint
name="roles.route"
props={extensionProps}
renderAll={true}
/>
</div>
</div>
</>
);
}
}
const mapStateToProps = (state, ownProps) => {
const roleName = ownProps.match.params.role;
const role = getRoleByName(state, roleName);
const loading = isFetchRolePending(state, roleName);
const error = getFetchRoleFailure(state, roleName);
const repositoryRolesLink = getRepositoryRolesLink(state);
return {
repositoryRolesLink,
roleName,
role,
loading,
error
};
};
const mapDispatchToProps = dispatch => {
return {
fetchRoleByName: (link: string, name: string) => {
dispatch(fetchRoleByName(link, name));
}
};
};
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(translate("config")(SingleRepositoryRole))
);

View File

@@ -4,7 +4,11 @@ import { isPending } from "../../../modules/pending";
import { getFailure } from "../../../modules/failure"; import { getFailure } from "../../../modules/failure";
import * as types from "../../../modules/types"; import * as types from "../../../modules/types";
import { combineReducers, Dispatch } from "redux"; import { combineReducers, Dispatch } from "redux";
import type {Action, PagedCollection, Repository, Role} from "@scm-manager/ui-types"; import type {
Action,
PagedCollection,
RepositoryRole
} from "@scm-manager/ui-types";
export const FETCH_ROLES = "scm/roles/FETCH_ROLES"; export const FETCH_ROLES = "scm/roles/FETCH_ROLES";
export const FETCH_ROLES_PENDING = `${FETCH_ROLES}_${types.PENDING_SUFFIX}`; export const FETCH_ROLES_PENDING = `${FETCH_ROLES}_${types.PENDING_SUFFIX}`;
@@ -138,12 +142,12 @@ export function fetchRoleByName(link: string, name: string) {
return fetchRole(roleUrl, name); return fetchRole(roleUrl, name);
} }
export function fetchRoleByLink(role: Role) { export function fetchRoleByLink(role: RepositoryRole) {
return fetchRole(role._links.self.href, role.name); return fetchRole(role._links.self.href, role.name);
} }
// create role // create role
export function createRolePending(role: Role): Action { export function createRolePending(role: RepositoryRole): Action {
return { return {
type: CREATE_ROLE_PENDING, type: CREATE_ROLE_PENDING,
role role
@@ -169,7 +173,11 @@ export function createRoleReset() {
}; };
} }
export function createRole(link: string, role: Role, callback?: () => void) { export function createRole(
link: string,
role: RepositoryRole,
callback?: () => void
) {
return function(dispatch: Dispatch) { return function(dispatch: Dispatch) {
dispatch(createRolePending(role)); dispatch(createRolePending(role));
return apiClient return apiClient
@@ -233,7 +241,7 @@ function verbReducer(state: any = {}, action: any = {}) {
} }
// modify role // modify role
export function modifyRolePending(role: Role): Action { export function modifyRolePending(role: RepositoryRole): Action {
return { return {
type: MODIFY_ROLE_PENDING, type: MODIFY_ROLE_PENDING,
payload: role, payload: role,
@@ -241,7 +249,7 @@ export function modifyRolePending(role: Role): Action {
}; };
} }
export function modifyRoleSuccess(role: Role): Action { export function modifyRoleSuccess(role: RepositoryRole): Action {
return { return {
type: MODIFY_ROLE_SUCCESS, type: MODIFY_ROLE_SUCCESS,
payload: role, payload: role,
@@ -249,7 +257,7 @@ export function modifyRoleSuccess(role: Role): Action {
}; };
} }
export function modifyRoleFailure(role: Role, error: Error): Action { export function modifyRoleFailure(role: RepositoryRole, error: Error): Action {
return { return {
type: MODIFY_ROLE_FAILURE, type: MODIFY_ROLE_FAILURE,
payload: { payload: {
@@ -260,14 +268,14 @@ export function modifyRoleFailure(role: Role, error: Error): Action {
}; };
} }
export function modifyRoleReset(role: Role): Action { export function modifyRoleReset(role: RepositoryRole): Action {
return { return {
type: MODIFY_ROLE_RESET, type: MODIFY_ROLE_RESET,
itemId: role.name itemId: role.name
}; };
} }
export function modifyRole(role: Role, callback?: () => void) { export function modifyRole(role: RepositoryRole, callback?: () => void) {
return function(dispatch: Dispatch) { return function(dispatch: Dispatch) {
dispatch(modifyRolePending(role)); dispatch(modifyRolePending(role));
return apiClient return apiClient
@@ -288,7 +296,7 @@ export function modifyRole(role: Role, callback?: () => void) {
} }
// delete role // delete role
export function deleteRolePending(role: Role): Action { export function deleteRolePending(role: RepositoryRole): Action {
return { return {
type: DELETE_ROLE_PENDING, type: DELETE_ROLE_PENDING,
payload: role, payload: role,
@@ -296,7 +304,7 @@ export function deleteRolePending(role: Role): Action {
}; };
} }
export function deleteRoleSuccess(role: Role): Action { export function deleteRoleSuccess(role: RepositoryRole): Action {
return { return {
type: DELETE_ROLE_SUCCESS, type: DELETE_ROLE_SUCCESS,
payload: role, payload: role,
@@ -304,7 +312,7 @@ export function deleteRoleSuccess(role: Role): Action {
}; };
} }
export function deleteRoleFailure(role: Role, error: Error): Action { export function deleteRoleFailure(role: RepositoryRole, error: Error): Action {
return { return {
type: DELETE_ROLE_FAILURE, type: DELETE_ROLE_FAILURE,
payload: { payload: {
@@ -315,7 +323,7 @@ export function deleteRoleFailure(role: Role, error: Error): Action {
}; };
} }
export function deleteRole(role: Role, callback?: () => void) { export function deleteRole(role: RepositoryRole, callback?: () => void) {
return function(dispatch: any) { return function(dispatch: any) {
dispatch(deleteRolePending(role)); dispatch(deleteRolePending(role));
return apiClient return apiClient
@@ -333,7 +341,7 @@ export function deleteRole(role: Role, callback?: () => void) {
} }
function extractRolesByNames( function extractRolesByNames(
roles: Role[], roles: RepositoryRole[],
roleNames: string[], roleNames: string[],
oldRolesByNames: Object oldRolesByNames: Object
) { ) {
@@ -460,7 +468,7 @@ export function getRolesFromState(state: Object) {
if (!roleNames) { if (!roleNames) {
return null; return null;
} }
const roleEntries: Role[] = []; const roleEntries: RepositoryRole[] = [];
for (let roleName of roleNames) { for (let roleName of roleNames) {
roleEntries.push(state.roles.byNames[roleName]); roleEntries.push(state.roles.byNames[roleName]);
@@ -470,12 +478,7 @@ export function getRolesFromState(state: Object) {
} }
export function getRoleCreateLink(state: Object) { export function getRoleCreateLink(state: Object) {
if ( if (state && state.list && state.list._links && state.list._links.create) {
state &&
state.list &&
state.list._links &&
state.list._links.create
) {
return state.list._links.create.href; return state.list._links.create.href;
} }
} }

View File

@@ -307,10 +307,9 @@ const reduceByBranchesSuccess = (state, payload) => {
const byName = repoState.byName || {}; const byName = repoState.byName || {};
repoState.byName = byName; repoState.byName = byName;
if(response._embedded) { if (response._embedded) {
const branches = response._embedded.branches; const branches = response._embedded.branches;
const names = branches.map(b => b.name); response._embedded.branches = branches.map(b => b.name);
response._embedded.branches = names;
for (let branch of branches) { for (let branch of branches) {
byName[branch.name] = branch; byName[branch.name] = branch;
} }

View File

@@ -1,10 +1,19 @@
// @flow // @flow
import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; import {
import {apiClient, urls} from "@scm-manager/ui-components"; FAILURE_SUFFIX,
import {isPending} from "../../modules/pending"; PENDING_SUFFIX,
import {getFailure} from "../../modules/failure"; SUCCESS_SUFFIX
import type {Action, Branch, PagedCollection, Repository} from "@scm-manager/ui-types"; } from "../../modules/types";
import { apiClient, urls } from "@scm-manager/ui-components";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
import type {
Action,
Branch,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;

View File

@@ -353,15 +353,13 @@ function normalizeByNamespaceAndName(
const reducerByNames = (state: Object, repository: Repository) => { const reducerByNames = (state: Object, repository: Repository) => {
const identifier = createIdentifier(repository); const identifier = createIdentifier(repository);
const newState = { return {
...state, ...state,
byNames: { byNames: {
...state.byNames, ...state.byNames,
[identifier]: repository [identifier]: repository
} }
}; };
return newState;
}; };
export default function reducer( export default function reducer(

View File

@@ -49,10 +49,7 @@ export function shouldFetchRepositoryTypes(state: Object) {
) { ) {
return false; return false;
} }
if (state.repositoryTypes && state.repositoryTypes.length > 0) { return !(state.repositoryTypes && state.repositoryTypes.length > 0);
return false;
}
return true;
} }
export function fetchRepositoryTypesPending(): Action { export function fetchRepositoryTypesPending(): Action {

View File

@@ -26,9 +26,11 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
const verbs = {}; const verbs = {};
props.availableVerbs.forEach( props.availableVerbs.forEach(
verb => (verbs[verb] = props.selectedVerbs.includes(verb)) verb =>
(verbs[verb] = props.selectedVerbs
? props.selectedVerbs.includes(verb)
: false)
); );
this.state = { verbs }; this.state = { verbs };
} }

View File

@@ -1,6 +1,12 @@
// @flow // @flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type {
RepositoryRole,
PermissionCollection,
PermissionCreateEntry,
SelectValue
} from "@scm-manager/ui-types";
import { import {
Subtitle, Subtitle,
Autocomplete, Autocomplete,
@@ -9,31 +15,28 @@ import {
LabelWithHelpIcon, LabelWithHelpIcon,
Radio Radio
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import RoleSelector from "../components/RoleSelector";
import type {
RepositoryRole,
PermissionCollection,
PermissionCreateEntry,
SelectValue
} from "@scm-manager/ui-types";
import * as validator from "../components/permissionValidation"; import * as validator from "../components/permissionValidation";
import { findMatchingRoleName } from "../modules/permissions"; import RoleSelector from "../components/RoleSelector";
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog"; import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
import { findVerbsForRole } from "../modules/permissions";
type Props = { type Props = {
t: string => string,
availableRoles: RepositoryRole[], availableRoles: RepositoryRole[],
availableVerbs: string[], availableVerbs: string[],
createPermission: (permission: PermissionCreateEntry) => void, createPermission: (permission: PermissionCreateEntry) => void,
loading: boolean, loading: boolean,
currentPermissions: PermissionCollection, currentPermissions: PermissionCollection,
groupAutoCompleteLink: string, groupAutoCompleteLink: string,
userAutoCompleteLink: string userAutoCompleteLink: string,
// Context props
t: string => string
}; };
type State = { type State = {
name: string, name: string,
verbs: string[], role?: string,
verbs?: string[],
groupPermission: boolean, groupPermission: boolean,
valid: boolean, valid: boolean,
value?: SelectValue, value?: SelectValue,
@@ -46,7 +49,8 @@ class CreatePermissionForm extends React.Component<Props, State> {
this.state = { this.state = {
name: "", name: "",
verbs: props.availableRoles[0].verbs, role: props.availableRoles[0].name,
verbs: undefined,
groupPermission: false, groupPermission: false,
valid: true, valid: true,
value: undefined, value: undefined,
@@ -91,6 +95,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
}); });
}); });
} }
renderAutocompletionField = () => { renderAutocompletionField = () => {
const { t } = this.props; const { t } = this.props;
if (this.state.groupPermission) { if (this.state.groupPermission) {
@@ -135,18 +140,16 @@ class CreatePermissionForm extends React.Component<Props, State> {
render() { render() {
const { t, availableRoles, availableVerbs, loading } = this.props; const { t, availableRoles, availableVerbs, loading } = this.props;
const { role, verbs, showAdvancedDialog } = this.state;
const { verbs, showAdvancedDialog } = this.state; const availableRoleNames = availableRoles.map(r => r.name);
const availableRoleNames = availableRoles.map( const selectedVerbs = role ? findVerbsForRole(availableRoles, role) : verbs;
r => r.name
);
const matchingRole = findMatchingRoleName(availableRoles, verbs);
const advancedDialog = showAdvancedDialog ? ( const advancedDialog = showAdvancedDialog ? (
<AdvancedPermissionsDialog <AdvancedPermissionsDialog
availableVerbs={availableVerbs} availableVerbs={availableVerbs}
selectedVerbs={verbs} selectedVerbs={selectedVerbs}
onClose={this.closeAdvancedPermissionsDialog} onClose={this.closeAdvancedPermissionsDialog}
onSubmit={this.submitAdvancedPermissionsDialog} onSubmit={this.submitAdvancedPermissionsDialog}
/> />
@@ -188,7 +191,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
label={t("permission.role")} label={t("permission.role")}
helpText={t("permission.help.roleHelpText")} helpText={t("permission.help.roleHelpText")}
handleRoleChange={this.handleRoleChange} handleRoleChange={this.handleRoleChange}
role={matchingRole} role={role}
/> />
</div> </div>
<div className="column"> <div className="column">
@@ -229,6 +232,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
submitAdvancedPermissionsDialog = (newVerbs: string[]) => { submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
this.setState({ this.setState({
showAdvancedDialog: false, showAdvancedDialog: false,
role: undefined,
verbs: newVerbs verbs: newVerbs
}); });
}; };
@@ -236,6 +240,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
submit = e => { submit = e => {
this.props.createPermission({ this.props.createPermission({
name: this.state.name, name: this.state.name,
role: this.state.role,
verbs: this.state.verbs, verbs: this.state.verbs,
groupPermission: this.state.groupPermission groupPermission: this.state.groupPermission
}); });
@@ -246,7 +251,8 @@ class CreatePermissionForm extends React.Component<Props, State> {
removeState = () => { removeState = () => {
this.setState({ this.setState({
name: "", name: "",
verbs: this.props.availableRoles[0].verbs, role: this.props.availableRoles[0].name,
verbs: undefined,
valid: true, valid: true,
value: undefined value: undefined
}); });
@@ -258,14 +264,13 @@ class CreatePermissionForm extends React.Component<Props, State> {
return; return;
} }
this.setState({ this.setState({
verbs: selectedRole.verbs role: selectedRole.name,
verbs: []
}); });
}; };
findAvailableRole = (roleName: string) => { findAvailableRole = (roleName: string) => {
return this.props.availableRoles.find( return this.props.availableRoles.find(role => role.name === roleName);
role => role.name === roleName
);
}; };
} }

View File

@@ -19,7 +19,9 @@ import {
getDeletePermissionsFailure, getDeletePermissionsFailure,
getModifyPermissionsFailure, getModifyPermissionsFailure,
modifyPermissionReset, modifyPermissionReset,
deletePermissionReset, getAvailableRepositoryRoles, getAvailableRepositoryVerbs deletePermissionReset,
getAvailableRepositoryRoles,
getAvailableRepositoryVerbs
} from "../modules/permissions"; } from "../modules/permissions";
import { import {
Loading, Loading,
@@ -38,11 +40,14 @@ import CreatePermissionForm from "./CreatePermissionForm";
import type { History } from "history"; import type { History } from "history";
import { getPermissionsLink } from "../../modules/repos"; import { getPermissionsLink } from "../../modules/repos";
import { import {
getGroupAutoCompleteLink, getRepositoryRolesLink, getRepositoryVerbsLink, getGroupAutoCompleteLink,
getRepositoryRolesLink,
getRepositoryVerbsLink,
getUserAutoCompleteLink getUserAutoCompleteLink
} from "../../../modules/indexResource"; } from "../../../modules/indexResource";
type Props = { type Props = {
availablePermissions: boolean,
availableRepositoryRoles: RepositoryRole[], availableRepositoryRoles: RepositoryRole[],
availableVerbs: string[], availableVerbs: string[],
namespace: string, namespace: string,
@@ -59,7 +64,10 @@ type Props = {
userAutoCompleteLink: string, userAutoCompleteLink: string,
//dispatch functions //dispatch functions
fetchAvailablePermissionsIfNeeded: () => void, fetchAvailablePermissionsIfNeeded: (
repositoryRolesLink: string,
repositoryVerbsLink: string
) => void,
fetchPermissions: (link: string, namespace: string, repoName: string) => void, fetchPermissions: (link: string, namespace: string, repoName: string) => void,
createPermission: ( createPermission: (
link: string, link: string,
@@ -77,7 +85,6 @@ type Props = {
history: History history: History
}; };
class Permissions extends React.Component<Props> { class Permissions extends React.Component<Props> {
componentDidMount() { componentDidMount() {
const { const {
@@ -251,8 +258,16 @@ const mapDispatchToProps = dispatch => {
fetchPermissions: (link: string, namespace: string, repoName: string) => { fetchPermissions: (link: string, namespace: string, repoName: string) => {
dispatch(fetchPermissions(link, namespace, repoName)); dispatch(fetchPermissions(link, namespace, repoName));
}, },
fetchAvailablePermissionsIfNeeded: (repositoryRolesLink: string, repositoryVerbsLink: string) => { fetchAvailablePermissionsIfNeeded: (
dispatch(fetchAvailablePermissionsIfNeeded(repositoryRolesLink, repositoryVerbsLink)); repositoryRolesLink: string,
repositoryVerbsLink: string
) => {
dispatch(
fetchAvailablePermissionsIfNeeded(
repositoryRolesLink,
repositoryVerbsLink
)
);
}, },
createPermission: ( createPermission: (
link: string, link: string,

View File

@@ -1,16 +1,13 @@
// @flow // @flow
import React from "react"; import React from "react";
import type { import type { RepositoryRole, Permission } from "@scm-manager/ui-types";
RepositoryRole,
Permission
} from "@scm-manager/ui-types";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { import {
modifyPermission, modifyPermission,
isModifyPermissionPending, isModifyPermissionPending,
deletePermission, deletePermission,
isDeletePermissionPending, isDeletePermissionPending,
findMatchingRoleName findVerbsForRole
} from "../modules/permissions"; } from "../modules/permissions";
import { connect } from "react-redux"; import { connect } from "react-redux";
import type { History } from "history"; import type { History } from "history";
@@ -47,7 +44,6 @@ type Props = {
}; };
type State = { type State = {
role: string,
permission: Permission, permission: Permission,
showAdvancedDialog: boolean showAdvancedDialog: boolean
}; };
@@ -69,39 +65,34 @@ class SinglePermission extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
const defaultPermission = props.availableRoles const defaultPermission = props.availableRepositoryRoles
? props.availableRoles[0] ? props.availableRepositoryRoles[0]
: {}; : {};
this.state = { this.state = {
permission: { permission: {
name: "", name: "",
role: undefined,
verbs: defaultPermission.verbs, verbs: defaultPermission.verbs,
groupPermission: false, groupPermission: false,
_links: {} _links: {}
}, },
role: defaultPermission.name,
showAdvancedDialog: false showAdvancedDialog: false
}; };
} }
componentDidMount() { componentDidMount() {
const { availableRepositoryRoles, permission } = this.props; const { permission } = this.props;
const matchingRole = findMatchingRoleName(
availableRepositoryRoles,
permission.verbs
);
if (permission) { if (permission) {
this.setState({ this.setState({
permission: { permission: {
name: permission.name, name: permission.name,
role: permission.role,
verbs: permission.verbs, verbs: permission.verbs,
groupPermission: permission.groupPermission, groupPermission: permission.groupPermission,
_links: permission._links _links: permission._links
}, }
role: matchingRole
}); });
} }
} }
@@ -115,7 +106,7 @@ class SinglePermission extends React.Component<Props, State> {
}; };
render() { render() {
const { role, permission, showAdvancedDialog } = this.state; const { permission, showAdvancedDialog } = this.state;
const { const {
t, t,
availableRepositoryRoles, availableRepositoryRoles,
@@ -125,28 +116,31 @@ class SinglePermission extends React.Component<Props, State> {
repoName, repoName,
classes classes
} = this.props; } = this.props;
const availableRoleNames = !!availableRepositoryRoles && availableRepositoryRoles.map( const availableRoleNames =
r => r.name !!availableRepositoryRoles && availableRepositoryRoles.map(r => r.name);
);
const readOnly = !this.mayChangePermissions(); const readOnly = !this.mayChangePermissions();
const roleSelector = readOnly ? ( const roleSelector = readOnly ? (
<td>{role}</td> <td>{permission.role ? permission.role : t("permission.custom")}</td>
) : ( ) : (
<td> <td>
<RoleSelector <RoleSelector
handleRoleChange={this.handleRoleChange} handleRoleChange={this.handleRoleChange}
availableRoles={availableRoleNames} availableRoles={availableRoleNames}
role={role} role={permission.role}
loading={loading} loading={loading}
/> />
</td> </td>
); );
const advancedDialg = showAdvancedDialog ? ( const selectedVerbs = permission.role
? findVerbsForRole(availableRepositoryRoles, permission.role)
: permission.verbs;
const advancedDialog = showAdvancedDialog ? (
<AdvancedPermissionsDialog <AdvancedPermissionsDialog
readOnly={readOnly} readOnly={readOnly}
availableVerbs={availableRepositoryVerbs} availableVerbs={availableRepositoryVerbs}
selectedVerbs={permission.verbs} selectedVerbs={selectedVerbs}
onClose={this.closeAdvancedPermissionsDialog} onClose={this.closeAdvancedPermissionsDialog}
onSubmit={this.submitAdvancedPermissionsDialog} onSubmit={this.submitAdvancedPermissionsDialog}
/> />
@@ -154,9 +148,15 @@ class SinglePermission extends React.Component<Props, State> {
const iconType = const iconType =
permission && permission.groupPermission ? ( permission && permission.groupPermission ? (
<i title={t("permission.group")} className={classNames("fas fa-user-friends", classes.iconColor)} /> <i
title={t("permission.group")}
className={classNames("fas fa-user-friends", classes.iconColor)}
/>
) : ( ) : (
<i title={t("permission.user")} className={classNames("fas fa-user", classes.iconColor)} /> <i
title={t("permission.user")}
className={classNames("fas fa-user", classes.iconColor)}
/>
); );
return ( return (
@@ -179,7 +179,7 @@ class SinglePermission extends React.Component<Props, State> {
deletePermission={this.deletePermission} deletePermission={this.deletePermission}
loading={this.props.deleteLoading} loading={this.props.deleteLoading}
/> />
{advancedDialg} {advancedDialog}
</td> </td>
</tr> </tr>
); );
@@ -199,42 +199,41 @@ class SinglePermission extends React.Component<Props, State> {
submitAdvancedPermissionsDialog = (newVerbs: string[]) => { submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
const { permission } = this.state; const { permission } = this.state;
const newRole = findMatchingRoleName(
this.props.availableRoles,
newVerbs
);
this.setState( this.setState(
{ {
showAdvancedDialog: false, showAdvancedDialog: false,
permission: { ...permission, verbs: newVerbs }, permission: { ...permission, role: undefined, verbs: newVerbs }
role: newRole
}, },
() => this.modifyPermission(newVerbs) () => this.modifyPermissionVerbs(newVerbs)
); );
}; };
handleRoleChange = (role: string) => { handleRoleChange = (role: string) => {
const selectedRole = this.findAvailableRole(role); const { permission } = this.state;
this.setState( this.setState(
{ {
permission: { permission: { ...permission, role: role, verbs: undefined }
...this.state.permission,
verbs: selectedRole.verbs
}, },
role: role () => this.modifyPermissionRole(role)
},
() => this.modifyPermission(selectedRole.verbs)
); );
}; };
findAvailableRole = (roleName: string) => { findAvailableRole = (roleName: string) => {
const { availableRepositoryRoles } = this.props; const { availableRepositoryRoles } = this.props;
return availableRepositoryRoles.find( return availableRepositoryRoles.find(role => role.name === roleName);
role => role.name === roleName };
modifyPermissionRole = (role: string) => {
let permission = this.state.permission;
permission.role = role;
this.props.modifyPermission(
permission,
this.props.namespace,
this.props.repoName
); );
}; };
modifyPermission = (verbs: string[]) => { modifyPermissionVerbs = (verbs: string[]) => {
let permission = this.state.permission; let permission = this.state.permission;
permission.verbs = verbs; permission.verbs = verbs;
this.props.modifyPermission( this.props.modifyPermission(

View File

@@ -77,10 +77,18 @@ const CONTENT_TYPE = "application/vnd.scmm-repositoryPermission+json";
// fetch available permissions // fetch available permissions
export function fetchAvailablePermissionsIfNeeded(repositoryRolesLink: string, repositoryVerbsLink: string) { export function fetchAvailablePermissionsIfNeeded(
repositoryRolesLink: string,
repositoryVerbsLink: string
) {
return function(dispatch: any, getState: () => Object) { return function(dispatch: any, getState: () => Object) {
if (shouldFetchAvailablePermissions(getState())) { if (shouldFetchAvailablePermissions(getState())) {
return fetchAvailablePermissions(dispatch, getState, repositoryRolesLink, repositoryVerbsLink); return fetchAvailablePermissions(
dispatch,
getState,
repositoryRolesLink,
repositoryVerbsLink
);
} }
}; };
} }
@@ -97,7 +105,8 @@ export function fetchAvailablePermissions(
.then(repositoryRoles => repositoryRoles.json()) .then(repositoryRoles => repositoryRoles.json())
.then(repositoryRoles => repositoryRoles._embedded.repositoryRoles) .then(repositoryRoles => repositoryRoles._embedded.repositoryRoles)
.then(repositoryRoles => { .then(repositoryRoles => {
return apiClient.get(repositoryVerbsLink) return apiClient
.get(repositoryVerbsLink)
.then(repositoryVerbs => repositoryVerbs.json()) .then(repositoryVerbs => repositoryVerbs.json())
.then(repositoryVerbs => repositoryVerbs.verbs) .then(repositoryVerbs => repositoryVerbs.verbs)
.then(repositoryVerbs => { .then(repositoryVerbs => {
@@ -577,8 +586,7 @@ export function getPermissionsOfRepo(
repoName: string repoName: string
) { ) {
if (state.permissions && state.permissions[namespace + "/" + repoName]) { if (state.permissions && state.permissions[namespace + "/" + repoName]) {
const permissions = state.permissions[namespace + "/" + repoName].entries; return state.permissions[namespace + "/" + repoName].entries;
return permissions;
} }
} }
@@ -732,32 +740,16 @@ export function getModifyPermissionsFailure(
return null; return null;
} }
export function findMatchingRoleName( export function findVerbsForRole(
availableRoles: RepositoryRole[], availableRepositoryRoles: RepositoryRole[],
verbs: string[] roleName: string
) { ) {
if (!verbs) { const matchingRole = availableRepositoryRoles.find(
return ""; role => roleName === role.name
} );
const matchingRole = !! availableRoles && availableRoles.find(role => {
return equalVerbs(role.verbs, verbs);
});
if (matchingRole) { if (matchingRole) {
return matchingRole.name; return matchingRole.verbs;
} else { } else {
return ""; return [];
} }
} }
function equalVerbs(verbs1: string[], verbs2: string[]) {
if (!verbs1 || !verbs2) {
return false;
}
if (verbs1.length !== verbs2.length) {
return false;
}
return verbs1.every(verb => verbs2.includes(verb));
}

View File

@@ -106,7 +106,7 @@ public class RepositoryCollectionResource {
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) { private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null); Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), singletonList("*"), false))); repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), "OWNER", false)));
return repository; return repository;
} }

View File

@@ -332,7 +332,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.hasSize(1) .hasSize(1)
.allSatisfy(p -> { .allSatisfy(p -> {
assertThat(p.getName()).isEqualTo("trillian"); assertThat(p.getName()).isEqualTo("trillian");
assertThat(p.getVerbs()).containsExactly("*"); assertThat(p.getRole()).isEqualTo("OWNER");
}); });
} }