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,
className?: string,
children?: React.Node,
// context props
classes: any
};

View File

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

View File

@@ -1,10 +1,13 @@
// @flow
import type {Links} from "./hal";
export type RepositoryRole = {
name: string,
verbs: string[],
type?: string,
creationDate?: 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 { Config } from "./Config";
export type { Role } from "./Role";
export type { IndexResources } from "./IndexResources";

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
// @flow
import React from "react";
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 type { History } from "history";
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 { getLinks } from "../../modules/indexResource";
import GlobalConfig from "./GlobalConfig";
import PermissionRolesOverview from "../roles/containers/PermissionRolesOverview";
import PermissionRoleRoot from "../roles/containers/PermissionRoleRoot";
import CreatePermissionRole from "../roles/containers/CreatePermissionRole";
import RepositoryRoles from "../roles/containers/RepositoryRoles";
import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
type Props = {
links: Links,
@@ -37,7 +37,7 @@ class Config extends React.Component<Props> {
matchesRoles = (route: any) => {
const url = this.matchedUrl();
const regex = new RegExp(`${url}/role/.+/info`);
const regex = new RegExp(`${url}/role/`);
return route.location.pathname.match(regex);
};
@@ -54,25 +54,44 @@ class Config extends React.Component<Props> {
<Page>
<div className="columns">
<div className="column is-three-quarters">
<Switch>
<Route path={url} exact component={GlobalConfig} />
<Route
path={`${url}/role/:role`}
component={() => <PermissionRoleRoot baseUrl={`${url}/roles`} />}
render={() => (
<SingleRepositoryRole
baseUrl={`${url}/roles`}
history={this.props.history}
/>
)}
/>
<Route
path={`${url}/roles`}
exact
render={() => <PermissionRolesOverview baseUrl={`${url}/role`} />}
render={() => <RepositoryRoles baseUrl={`${url}/roles`} />}
/>
<Route
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
name="config.route"
props={extensionProps}
renderAll={true}
/>
</Switch>
</div>
<div className="column is-one-quarter">
<Navigation>
@@ -83,9 +102,8 @@ class Config extends React.Component<Props> {
/>
<NavLink
to={`${url}/roles/`}
label={t("roles.navLink")}
label={t("repositoryRole.navLink")}
activeWhenMatch={this.matchesRoles}
activeOnlyWhenExact={false}
/>
<ExtensionPoint
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
import React from "react";
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";
type Props = {
baseUrl: string,
role: Role
role: RepositoryRole
};
class PermissionRoleRow extends React.Component<Props> {
@@ -20,7 +20,8 @@ class PermissionRoleRow extends React.Component<Props> {
render() {
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 (
<tr>
<td>{this.renderLink(to, role.name, !role._links.update)}</td>

View File

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

View File

@@ -4,7 +4,7 @@ import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import { translate } from "react-i18next";
import type { History } from "history";
import type { Role, PagedCollection } from "@scm-manager/ui-types";
import type { RepositoryRole, PagedCollection } from "@scm-manager/ui-types";
import {
Title,
Loading,
@@ -23,10 +23,9 @@ import {
} from "../modules/roles";
import PermissionRoleTable from "../components/PermissionRoleTable";
import { getRolesLink } from "../../../modules/indexResource";
type Props = {
baseUrl: string,
roles: Role[],
roles: RepositoryRole[],
loading: boolean,
error: Error,
canAddRoles: boolean,
@@ -42,12 +41,33 @@ type Props = {
fetchRolesByPage: (link: string, page: number) => void
};
class PermissionRolesOverview extends React.Component<Props> {
class RepositoryRoles extends React.Component<Props> {
componentDidMount() {
const { fetchRolesByPage, rolesLink, page } = this.props;
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() {
const { t, loading } = this.props;
@@ -57,7 +77,7 @@ class PermissionRolesOverview extends React.Component<Props> {
return (
<div>
<Title title={t("roles.title")} />
<Title title={t("repositoryRole.title")} />
{this.renderPermissionsTable()}
{this.renderCreateButton()}
</div>
@@ -75,14 +95,21 @@ class PermissionRolesOverview extends React.Component<Props> {
);
}
return (
<Notification type="info">{t("roles.noPermissionRoles")}</Notification>
<Notification type="info">
{t("repositoryRole.noPermissionRoles")}
</Notification>
);
}
renderCreateButton() {
const { canAddRoles, baseUrl, t } = this.props;
if (canAddRoles) {
return <CreateButton label={t("roles.createButton")} link={`${baseUrl}/create`} />;
return (
<CreateButton
label={t("repositoryRole.createButton")}
link={`${baseUrl}/create`}
/>
);
}
return null;
}
@@ -117,7 +144,9 @@ const mapDispatchToProps = dispatch => {
};
};
export default withRouter(connect(
export default withRouter(
connect(
mapStateToProps,
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 * as types from "../../../modules/types";
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_PENDING = `${FETCH_ROLES}_${types.PENDING_SUFFIX}`;
@@ -138,12 +142,12 @@ export function fetchRoleByName(link: string, name: string) {
return fetchRole(roleUrl, name);
}
export function fetchRoleByLink(role: Role) {
export function fetchRoleByLink(role: RepositoryRole) {
return fetchRole(role._links.self.href, role.name);
}
// create role
export function createRolePending(role: Role): Action {
export function createRolePending(role: RepositoryRole): Action {
return {
type: CREATE_ROLE_PENDING,
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) {
dispatch(createRolePending(role));
return apiClient
@@ -233,7 +241,7 @@ function verbReducer(state: any = {}, action: any = {}) {
}
// modify role
export function modifyRolePending(role: Role): Action {
export function modifyRolePending(role: RepositoryRole): Action {
return {
type: MODIFY_ROLE_PENDING,
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 {
type: MODIFY_ROLE_SUCCESS,
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 {
type: MODIFY_ROLE_FAILURE,
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 {
type: MODIFY_ROLE_RESET,
itemId: role.name
};
}
export function modifyRole(role: Role, callback?: () => void) {
export function modifyRole(role: RepositoryRole, callback?: () => void) {
return function(dispatch: Dispatch) {
dispatch(modifyRolePending(role));
return apiClient
@@ -288,7 +296,7 @@ export function modifyRole(role: Role, callback?: () => void) {
}
// delete role
export function deleteRolePending(role: Role): Action {
export function deleteRolePending(role: RepositoryRole): Action {
return {
type: DELETE_ROLE_PENDING,
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 {
type: DELETE_ROLE_SUCCESS,
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 {
type: DELETE_ROLE_FAILURE,
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) {
dispatch(deleteRolePending(role));
return apiClient
@@ -333,7 +341,7 @@ export function deleteRole(role: Role, callback?: () => void) {
}
function extractRolesByNames(
roles: Role[],
roles: RepositoryRole[],
roleNames: string[],
oldRolesByNames: Object
) {
@@ -460,7 +468,7 @@ export function getRolesFromState(state: Object) {
if (!roleNames) {
return null;
}
const roleEntries: Role[] = [];
const roleEntries: RepositoryRole[] = [];
for (let roleName of roleNames) {
roleEntries.push(state.roles.byNames[roleName]);
@@ -470,12 +478,7 @@ export function getRolesFromState(state: Object) {
}
export function getRoleCreateLink(state: Object) {
if (
state &&
state.list &&
state.list._links &&
state.list._links.create
) {
if (state && state.list && state.list._links && state.list._links.create) {
return state.list._links.create.href;
}
}

View File

@@ -309,8 +309,7 @@ const reduceByBranchesSuccess = (state, payload) => {
if (response._embedded) {
const branches = response._embedded.branches;
const names = branches.map(b => b.name);
response._embedded.branches = names;
response._embedded.branches = branches.map(b => b.name);
for (let branch of branches) {
byName[branch.name] = branch;
}

View File

@@ -1,10 +1,19 @@
// @flow
import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types";
import {
FAILURE_SUFFIX,
PENDING_SUFFIX,
SUCCESS_SUFFIX
} 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";
import type {
Action,
Branch,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -77,10 +77,18 @@ const CONTENT_TYPE = "application/vnd.scmm-repositoryPermission+json";
// fetch available permissions
export function fetchAvailablePermissionsIfNeeded(repositoryRolesLink: string, repositoryVerbsLink: string) {
export function fetchAvailablePermissionsIfNeeded(
repositoryRolesLink: string,
repositoryVerbsLink: string
) {
return function(dispatch: any, getState: () => Object) {
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._embedded.repositoryRoles)
.then(repositoryRoles => {
return apiClient.get(repositoryVerbsLink)
return apiClient
.get(repositoryVerbsLink)
.then(repositoryVerbs => repositoryVerbs.json())
.then(repositoryVerbs => repositoryVerbs.verbs)
.then(repositoryVerbs => {
@@ -577,8 +586,7 @@ export function getPermissionsOfRepo(
repoName: string
) {
if (state.permissions && state.permissions[namespace + "/" + repoName]) {
const permissions = state.permissions[namespace + "/" + repoName].entries;
return permissions;
return state.permissions[namespace + "/" + repoName].entries;
}
}
@@ -732,32 +740,16 @@ export function getModifyPermissionsFailure(
return null;
}
export function findMatchingRoleName(
availableRoles: RepositoryRole[],
verbs: string[]
export function findVerbsForRole(
availableRepositoryRoles: RepositoryRole[],
roleName: string
) {
if (!verbs) {
return "";
}
const matchingRole = !! availableRoles && availableRoles.find(role => {
return equalVerbs(role.verbs, verbs);
});
const matchingRole = availableRepositoryRoles.find(
role => roleName === role.name
);
if (matchingRole) {
return matchingRole.name;
return matchingRole.verbs;
} 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) {
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), singletonList("*"), false)));
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), "OWNER", false)));
return repository;
}

View File

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