mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-18 03:01:05 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Checkbox } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
disabled: boolean,
|
||||
name: string,
|
||||
checked: boolean,
|
||||
onChange?: (value: boolean, name?: string) => void
|
||||
};
|
||||
|
||||
class PermissionCheckbox extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Checkbox
|
||||
key={this.props.name}
|
||||
name={this.props.name}
|
||||
helpText={t("verbs.repository." + this.props.name + ".description")}
|
||||
label={t("verbs.repository." + this.props.name + ".displayName")}
|
||||
checked={this.props.checked}
|
||||
onChange={this.props.onChange}
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("plugins")(PermissionCheckbox);
|
||||
55
scm-ui/src/repos/permissions/components/RoleSelector.js
Normal file
55
scm-ui/src/repos/permissions/components/RoleSelector.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Select } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
availableRoles: string[],
|
||||
handleRoleChange: string => void,
|
||||
role: string,
|
||||
label?: string,
|
||||
helpText?: string,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
class RoleSelector extends React.Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
availableRoles,
|
||||
role,
|
||||
handleRoleChange,
|
||||
loading,
|
||||
label,
|
||||
helpText
|
||||
} = this.props;
|
||||
|
||||
if (!availableRoles) return null;
|
||||
|
||||
const options = role
|
||||
? this.createSelectOptions(availableRoles)
|
||||
: ["", ...this.createSelectOptions(availableRoles)];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={handleRoleChange}
|
||||
value={role ? role : ""}
|
||||
options={options}
|
||||
loading={loading}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
createSelectOptions(roles: string[]) {
|
||||
return roles.map(role => {
|
||||
return {
|
||||
label: role,
|
||||
value: role
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(RoleSelector);
|
||||
@@ -1,42 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Select } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
handleTypeChange: string => void,
|
||||
type: string,
|
||||
label?: string,
|
||||
helpText?: string,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
class TypeSelector extends React.Component<Props> {
|
||||
render() {
|
||||
const { type, handleTypeChange, loading, label, helpText } = this.props;
|
||||
const types = ["READ", "OWNER", "WRITE"];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={handleTypeChange}
|
||||
value={type ? type : "READ"}
|
||||
options={this.createSelectOptions(types)}
|
||||
loading={loading}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
createSelectOptions(types: string[]) {
|
||||
return types.map(type => {
|
||||
return {
|
||||
label: type,
|
||||
value: type
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(TypeSelector);
|
||||
@@ -18,7 +18,8 @@ describe("permission validation", () => {
|
||||
name: "PermissionName",
|
||||
groupPermission: true,
|
||||
type: "READ",
|
||||
_links: {}
|
||||
_links: {},
|
||||
verbs: []
|
||||
}
|
||||
];
|
||||
const name = "PermissionName";
|
||||
@@ -35,7 +36,8 @@ describe("permission validation", () => {
|
||||
name: "PermissionName",
|
||||
groupPermission: false,
|
||||
type: "READ",
|
||||
_links: {}
|
||||
_links: {},
|
||||
verbs: []
|
||||
}
|
||||
];
|
||||
const name = "PermissionName";
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import { Button, SubmitButton } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import PermissionCheckbox from "../components/PermissionCheckbox";
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean,
|
||||
availableVerbs: string[],
|
||||
selectedVerbs: string[],
|
||||
onSubmit: (string[]) => void,
|
||||
onClose: () => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = {
|
||||
verbs: any
|
||||
};
|
||||
|
||||
class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const verbs = {};
|
||||
props.availableVerbs.forEach(
|
||||
verb => (verbs[verb] = props.selectedVerbs.includes(verb))
|
||||
);
|
||||
|
||||
this.state = { verbs };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, onClose, readOnly } = this.props;
|
||||
const { verbs } = this.state;
|
||||
|
||||
const verbSelectBoxes = Object.entries(verbs).map(e => (
|
||||
<PermissionCheckbox
|
||||
disabled={readOnly}
|
||||
name={e[0]}
|
||||
checked={e[1]}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
));
|
||||
|
||||
const submitButton = !readOnly ? (
|
||||
<SubmitButton label={t("permission.advanced.dialog.submit")} />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div className={"modal is-active"}>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-card">
|
||||
<header className="modal-card-head">
|
||||
<p className="modal-card-title">
|
||||
{t("permission.advanced.dialog.title")}
|
||||
</p>
|
||||
<button
|
||||
className="delete"
|
||||
aria-label="close"
|
||||
onClick={() => onClose()}
|
||||
/>
|
||||
</header>
|
||||
<section className="modal-card-body">
|
||||
<div className="content">{verbSelectBoxes}</div>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
{submitButton}
|
||||
<Button
|
||||
label={t("permission.advanced.dialog.abort")}
|
||||
action={onClose}
|
||||
/>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleChange = (value: boolean, name: string) => {
|
||||
const { verbs } = this.state;
|
||||
const newVerbs = { ...verbs, [name]: value };
|
||||
this.setState({ verbs: newVerbs });
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
this.props.onSubmit(
|
||||
Object.entries(this.state.verbs)
|
||||
.filter(e => e[1])
|
||||
.map(e => e[0])
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(AdvancedPermissionsDialog);
|
||||
@@ -1,17 +1,26 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Autocomplete, Radio, SubmitButton } from "@scm-manager/ui-components";
|
||||
import TypeSelector from "./TypeSelector";
|
||||
import {
|
||||
Autocomplete,
|
||||
SubmitButton,
|
||||
Button,
|
||||
LabelWithHelpIcon
|
||||
} from "@scm-manager/ui-components";
|
||||
import RoleSelector from "../components/RoleSelector";
|
||||
import type {
|
||||
AvailableRepositoryPermissions,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry,
|
||||
SelectValue
|
||||
} from "@scm-manager/ui-types";
|
||||
import * as validator from "./permissionValidation";
|
||||
import * as validator from "../components/permissionValidation";
|
||||
import { findMatchingRoleName } from "../modules/permissions";
|
||||
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
availablePermissions: AvailableRepositoryPermissions,
|
||||
createPermission: (permission: PermissionCreateEntry) => void,
|
||||
loading: boolean,
|
||||
currentPermissions: PermissionCollection,
|
||||
@@ -21,10 +30,11 @@ type Props = {
|
||||
|
||||
type State = {
|
||||
name: string,
|
||||
type: string,
|
||||
verbs: string[],
|
||||
groupPermission: boolean,
|
||||
valid: boolean,
|
||||
value?: SelectValue
|
||||
value?: SelectValue,
|
||||
showAdvancedDialog: boolean
|
||||
};
|
||||
|
||||
class CreatePermissionForm extends React.Component<Props, State> {
|
||||
@@ -33,10 +43,11 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
|
||||
this.state = {
|
||||
name: "",
|
||||
type: "READ",
|
||||
verbs: props.availablePermissions.availableRoles[0].verbs,
|
||||
groupPermission: false,
|
||||
valid: true,
|
||||
value: undefined
|
||||
value: undefined,
|
||||
showAdvancedDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,9 +132,23 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, loading } = this.props;
|
||||
const { t, availablePermissions, loading } = this.props;
|
||||
|
||||
const { type } = this.state;
|
||||
const { verbs, showAdvancedDialog } = this.state;
|
||||
|
||||
const availableRoleNames = availablePermissions.availableRoles.map(
|
||||
r => r.name
|
||||
);
|
||||
const matchingRole = findMatchingRoleName(availablePermissions, verbs);
|
||||
|
||||
const advancedDialog = showAdvancedDialog ? (
|
||||
<AdvancedPermissionsDialog
|
||||
availableVerbs={availablePermissions.availableVerbs}
|
||||
selectedVerbs={verbs}
|
||||
onClose={this.closeAdvancedPermissionsDialog}
|
||||
onSubmit={this.submitAdvancedPermissionsDialog}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -131,32 +156,57 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
<h2 className="subtitle">
|
||||
{t("permission.add-permission.add-permission-heading")}
|
||||
</h2>
|
||||
{advancedDialog}
|
||||
<form onSubmit={this.submit}>
|
||||
<Radio
|
||||
name="permission_scope"
|
||||
value="USER_PERMISSION"
|
||||
checked={!this.state.groupPermission}
|
||||
label={t("permission.user-permission")}
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
<Radio
|
||||
name="permission_scope"
|
||||
value="GROUP_PERMISSION"
|
||||
checked={this.state.groupPermission}
|
||||
label={t("permission.group-permission")}
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
<div className="control">
|
||||
<label className="radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="permission_scope"
|
||||
checked={!this.state.groupPermission}
|
||||
value="USER_PERMISSION"
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
{t("permission.user-permission")}
|
||||
</label>
|
||||
<label className="radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="permission_scope"
|
||||
value="GROUP_PERMISSION"
|
||||
checked={this.state.groupPermission}
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
{t("permission.group-permission")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<div className="column is-two-thirds">
|
||||
{this.renderAutocompletionField()}
|
||||
</div>
|
||||
<div className="column is-one-quarter">
|
||||
<TypeSelector
|
||||
label={t("permission.type")}
|
||||
helpText={t("permission.help.typeHelpText")}
|
||||
handleTypeChange={this.handleTypeChange}
|
||||
type={type ? type : "READ"}
|
||||
/>
|
||||
<div className="column is-one-third">
|
||||
<div className="columns">
|
||||
<div className="column is-half">
|
||||
<RoleSelector
|
||||
availableRoles={availableRoleNames}
|
||||
label={t("permission.role")}
|
||||
helpText={t("permission.help.roleHelpText")}
|
||||
handleRoleChange={this.handleRoleChange}
|
||||
role={matchingRole}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-half">
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.permissions")}
|
||||
helpText={t("permission.help.permissionsHelpText")}
|
||||
/>
|
||||
<Button
|
||||
label={t("permission.advanced-button.label")}
|
||||
action={this.handleDetailedPermissionsPressed}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
@@ -173,10 +223,25 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
handleDetailedPermissionsPressed = () => {
|
||||
this.setState({ showAdvancedDialog: true });
|
||||
};
|
||||
|
||||
closeAdvancedPermissionsDialog = () => {
|
||||
this.setState({ showAdvancedDialog: false });
|
||||
};
|
||||
|
||||
submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
|
||||
this.setState({
|
||||
showAdvancedDialog: false,
|
||||
verbs: newVerbs
|
||||
});
|
||||
};
|
||||
|
||||
submit = e => {
|
||||
this.props.createPermission({
|
||||
name: this.state.name,
|
||||
type: this.state.type,
|
||||
verbs: this.state.verbs,
|
||||
groupPermission: this.state.groupPermission
|
||||
});
|
||||
this.removeState();
|
||||
@@ -186,17 +251,24 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
removeState = () => {
|
||||
this.setState({
|
||||
name: "",
|
||||
type: "READ",
|
||||
verbs: this.props.availablePermissions.availableRoles[0].verbs,
|
||||
groupPermission: false,
|
||||
valid: true
|
||||
});
|
||||
};
|
||||
|
||||
handleTypeChange = (type: string) => {
|
||||
handleRoleChange = (role: string) => {
|
||||
const selectedRole = this.findAvailableRole(role);
|
||||
this.setState({
|
||||
type: type
|
||||
verbs: selectedRole.verbs
|
||||
});
|
||||
};
|
||||
|
||||
findAvailableRole = (roleName: string) => {
|
||||
return this.props.availablePermissions.availableRoles.find(
|
||||
role => role.name === roleName
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(CreatePermissionForm);
|
||||
@@ -1,225 +1,268 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
fetchPermissions,
|
||||
getFetchPermissionsFailure,
|
||||
isFetchPermissionsPending,
|
||||
getPermissionsOfRepo,
|
||||
hasCreatePermission,
|
||||
createPermission,
|
||||
isCreatePermissionPending,
|
||||
getCreatePermissionFailure,
|
||||
createPermissionReset,
|
||||
getDeletePermissionsFailure,
|
||||
getModifyPermissionsFailure,
|
||||
modifyPermissionReset,
|
||||
deletePermissionReset
|
||||
} from "../modules/permissions";
|
||||
import { Loading, ErrorPage } from "@scm-manager/ui-components";
|
||||
import type {
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
} from "@scm-manager/ui-types";
|
||||
import SinglePermission from "./SinglePermission";
|
||||
import CreatePermissionForm from "../components/CreatePermissionForm";
|
||||
import type { History } from "history";
|
||||
import { getPermissionsLink } from "../../modules/repos";
|
||||
import {
|
||||
getGroupAutoCompleteLink,
|
||||
getUserAutoCompleteLink
|
||||
} from "../../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
permissions: PermissionCollection,
|
||||
hasPermissionToCreate: boolean,
|
||||
loadingCreatePermission: boolean,
|
||||
permissionsLink: string,
|
||||
groupAutoCompleteLink: string,
|
||||
userAutoCompleteLink: string,
|
||||
|
||||
//dispatch functions
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => void,
|
||||
createPermissionReset: (string, string) => void,
|
||||
modifyPermissionReset: (string, string) => void,
|
||||
deletePermissionReset: (string, string) => void,
|
||||
// context props
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
class Permissions extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const {
|
||||
fetchPermissions,
|
||||
namespace,
|
||||
repoName,
|
||||
modifyPermissionReset,
|
||||
createPermissionReset,
|
||||
deletePermissionReset,
|
||||
permissionsLink
|
||||
} = this.props;
|
||||
|
||||
createPermissionReset(namespace, repoName);
|
||||
modifyPermissionReset(namespace, repoName);
|
||||
deletePermissionReset(namespace, repoName);
|
||||
fetchPermissions(permissionsLink, namespace, repoName);
|
||||
}
|
||||
|
||||
createPermission = (permission: Permission) => {
|
||||
this.props.createPermission(
|
||||
this.props.permissionsLink,
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
permissions,
|
||||
t,
|
||||
namespace,
|
||||
repoName,
|
||||
loadingCreatePermission,
|
||||
hasPermissionToCreate,
|
||||
userAutoCompleteLink,
|
||||
groupAutoCompleteLink
|
||||
} = this.props;
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("permission.error-title")}
|
||||
subtitle={t("permission.error-subtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || !permissions) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const createPermissionForm = hasPermissionToCreate ? (
|
||||
<CreatePermissionForm
|
||||
createPermission={permission => this.createPermission(permission)}
|
||||
loading={loadingCreatePermission}
|
||||
currentPermissions={permissions}
|
||||
userAutoCompleteLink={userAutoCompleteLink}
|
||||
groupAutoCompleteLink={groupAutoCompleteLink}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="has-background-light table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("permission.name")}</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("permission.group-permission")}
|
||||
</th>
|
||||
<th>{t("permission.type")}</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{permissions.map(permission => {
|
||||
return (
|
||||
<SinglePermission
|
||||
key={permission.name + permission.groupPermission.toString()}
|
||||
namespace={namespace}
|
||||
repoName={repoName}
|
||||
permission={permission}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{createPermissionForm}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const namespace = ownProps.namespace;
|
||||
const repoName = ownProps.repoName;
|
||||
const error =
|
||||
getFetchPermissionsFailure(state, namespace, repoName) ||
|
||||
getCreatePermissionFailure(state, namespace, repoName) ||
|
||||
getDeletePermissionsFailure(state, namespace, repoName) ||
|
||||
getModifyPermissionsFailure(state, namespace, repoName);
|
||||
const loading = isFetchPermissionsPending(state, namespace, repoName);
|
||||
const permissions = getPermissionsOfRepo(state, namespace, repoName);
|
||||
const loadingCreatePermission = isCreatePermissionPending(
|
||||
state,
|
||||
namespace,
|
||||
repoName
|
||||
);
|
||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||
const groupAutoCompleteLink = getGroupAutoCompleteLink(state);
|
||||
const userAutoCompleteLink = getUserAutoCompleteLink(state);
|
||||
return {
|
||||
namespace,
|
||||
repoName,
|
||||
error,
|
||||
loading,
|
||||
permissions,
|
||||
hasPermissionToCreate,
|
||||
loadingCreatePermission,
|
||||
permissionsLink,
|
||||
groupAutoCompleteLink,
|
||||
userAutoCompleteLink
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => {
|
||||
dispatch(fetchPermissions(link, namespace, repoName));
|
||||
},
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => {
|
||||
dispatch(
|
||||
createPermission(link, permission, namespace, repoName, callback)
|
||||
);
|
||||
},
|
||||
createPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(createPermissionReset(namespace, repoName));
|
||||
},
|
||||
modifyPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(modifyPermissionReset(namespace, repoName));
|
||||
},
|
||||
deletePermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(deletePermissionReset(namespace, repoName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(Permissions));
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
fetchAvailablePermissionsIfNeeded,
|
||||
fetchPermissions,
|
||||
getFetchAvailablePermissionsFailure,
|
||||
getAvailablePermissions,
|
||||
getFetchPermissionsFailure,
|
||||
isFetchAvailablePermissionsPending,
|
||||
isFetchPermissionsPending,
|
||||
getPermissionsOfRepo,
|
||||
hasCreatePermission,
|
||||
createPermission,
|
||||
isCreatePermissionPending,
|
||||
getCreatePermissionFailure,
|
||||
createPermissionReset,
|
||||
getDeletePermissionsFailure,
|
||||
getModifyPermissionsFailure,
|
||||
modifyPermissionReset,
|
||||
deletePermissionReset
|
||||
} from "../modules/permissions";
|
||||
import {
|
||||
Loading,
|
||||
ErrorPage,
|
||||
LabelWithHelpIcon
|
||||
} from "@scm-manager/ui-components";
|
||||
import type {
|
||||
AvailableRepositoryPermissions,
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
} from "@scm-manager/ui-types";
|
||||
import SinglePermission from "./SinglePermission";
|
||||
import CreatePermissionForm from "./CreatePermissionForm";
|
||||
import type { History } from "history";
|
||||
import { getPermissionsLink } from "../../modules/repos";
|
||||
import {
|
||||
getGroupAutoCompleteLink,
|
||||
getUserAutoCompleteLink
|
||||
} from "../../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
availablePermissions: AvailableRepositoryPermissions,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
permissions: PermissionCollection,
|
||||
hasPermissionToCreate: boolean,
|
||||
loadingCreatePermission: boolean,
|
||||
permissionsLink: string,
|
||||
groupAutoCompleteLink: string,
|
||||
userAutoCompleteLink: string,
|
||||
|
||||
//dispatch functions
|
||||
fetchAvailablePermissionsIfNeeded: () => void,
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => void,
|
||||
createPermissionReset: (string, string) => void,
|
||||
modifyPermissionReset: (string, string) => void,
|
||||
deletePermissionReset: (string, string) => void,
|
||||
// context props
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
class Permissions extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const {
|
||||
fetchAvailablePermissionsIfNeeded,
|
||||
fetchPermissions,
|
||||
namespace,
|
||||
repoName,
|
||||
modifyPermissionReset,
|
||||
createPermissionReset,
|
||||
deletePermissionReset,
|
||||
permissionsLink
|
||||
} = this.props;
|
||||
|
||||
createPermissionReset(namespace, repoName);
|
||||
modifyPermissionReset(namespace, repoName);
|
||||
deletePermissionReset(namespace, repoName);
|
||||
fetchAvailablePermissionsIfNeeded();
|
||||
fetchPermissions(permissionsLink, namespace, repoName);
|
||||
}
|
||||
|
||||
createPermission = (permission: Permission) => {
|
||||
this.props.createPermission(
|
||||
this.props.permissionsLink,
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
availablePermissions,
|
||||
loading,
|
||||
error,
|
||||
permissions,
|
||||
t,
|
||||
namespace,
|
||||
repoName,
|
||||
loadingCreatePermission,
|
||||
hasPermissionToCreate,
|
||||
userAutoCompleteLink,
|
||||
groupAutoCompleteLink
|
||||
} = this.props;
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("permission.error-title")}
|
||||
subtitle={t("permission.error-subtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || !permissions || !availablePermissions) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const createPermissionForm = hasPermissionToCreate ? (
|
||||
<CreatePermissionForm
|
||||
availablePermissions={availablePermissions}
|
||||
createPermission={permission => this.createPermission(permission)}
|
||||
loading={loadingCreatePermission}
|
||||
currentPermissions={permissions}
|
||||
userAutoCompleteLink={userAutoCompleteLink}
|
||||
groupAutoCompleteLink={groupAutoCompleteLink}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="has-background-light table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.name")}
|
||||
helpText={t("permission.help.nameHelpText")}
|
||||
/>
|
||||
</th>
|
||||
<th className="is-hidden-mobile">
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.group-permission")}
|
||||
helpText={t("permission.help.groupPermissionHelpText")}
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.role")}
|
||||
helpText={t("permission.help.roleHelpText")}
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.permissions")}
|
||||
helpText={t("permission.help.permissionsHelpText")}
|
||||
/>
|
||||
</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{permissions.map(permission => {
|
||||
return (
|
||||
<SinglePermission
|
||||
availablePermissions={availablePermissions}
|
||||
key={permission.name + permission.groupPermission.toString()}
|
||||
namespace={namespace}
|
||||
repoName={repoName}
|
||||
permission={permission}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{createPermissionForm}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const namespace = ownProps.namespace;
|
||||
const repoName = ownProps.repoName;
|
||||
const error =
|
||||
getFetchPermissionsFailure(state, namespace, repoName) ||
|
||||
getCreatePermissionFailure(state, namespace, repoName) ||
|
||||
getDeletePermissionsFailure(state, namespace, repoName) ||
|
||||
getModifyPermissionsFailure(state, namespace, repoName) ||
|
||||
getFetchAvailablePermissionsFailure(state);
|
||||
const loading =
|
||||
isFetchPermissionsPending(state, namespace, repoName) ||
|
||||
isFetchAvailablePermissionsPending(state);
|
||||
const permissions = getPermissionsOfRepo(state, namespace, repoName);
|
||||
const loadingCreatePermission = isCreatePermissionPending(
|
||||
state,
|
||||
namespace,
|
||||
repoName
|
||||
);
|
||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||
const groupAutoCompleteLink = getGroupAutoCompleteLink(state);
|
||||
const userAutoCompleteLink = getUserAutoCompleteLink(state);
|
||||
const availablePermissions = getAvailablePermissions(state);
|
||||
return {
|
||||
availablePermissions,
|
||||
namespace,
|
||||
repoName,
|
||||
error,
|
||||
loading,
|
||||
permissions,
|
||||
hasPermissionToCreate,
|
||||
loadingCreatePermission,
|
||||
permissionsLink,
|
||||
groupAutoCompleteLink,
|
||||
userAutoCompleteLink
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => {
|
||||
dispatch(fetchPermissions(link, namespace, repoName));
|
||||
},
|
||||
fetchAvailablePermissionsIfNeeded: () => {
|
||||
dispatch(fetchAvailablePermissionsIfNeeded());
|
||||
},
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => {
|
||||
dispatch(
|
||||
createPermission(link, permission, namespace, repoName, callback)
|
||||
);
|
||||
},
|
||||
createPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(createPermissionReset(namespace, repoName));
|
||||
},
|
||||
modifyPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(modifyPermissionReset(namespace, repoName));
|
||||
},
|
||||
deletePermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(deletePermissionReset(namespace, repoName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(Permissions));
|
||||
|
||||
@@ -1,176 +1,256 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type { Permission } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
modifyPermission,
|
||||
isModifyPermissionPending,
|
||||
deletePermission,
|
||||
isDeletePermissionPending
|
||||
} from "../modules/permissions";
|
||||
import { connect } from "react-redux";
|
||||
import type { History } from "history";
|
||||
import { Checkbox } from "@scm-manager/ui-components";
|
||||
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
|
||||
import TypeSelector from "../components/TypeSelector";
|
||||
|
||||
type Props = {
|
||||
submitForm: Permission => void,
|
||||
modifyPermission: (Permission, string, string) => void,
|
||||
permission: Permission,
|
||||
t: string => string,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
match: any,
|
||||
history: History,
|
||||
loading: boolean,
|
||||
deletePermission: (Permission, string, string) => void,
|
||||
deleteLoading: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
permission: Permission
|
||||
};
|
||||
|
||||
class SinglePermission extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
permission: {
|
||||
name: "",
|
||||
type: "READ",
|
||||
groupPermission: false,
|
||||
_links: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { permission } = this.props;
|
||||
if (permission) {
|
||||
this.setState({
|
||||
permission: {
|
||||
name: permission.name,
|
||||
type: permission.type,
|
||||
groupPermission: permission.groupPermission,
|
||||
_links: permission._links
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deletePermission = () => {
|
||||
this.props.deletePermission(
|
||||
this.props.permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { permission } = this.state;
|
||||
const { loading, namespace, repoName } = this.props;
|
||||
const typeSelector =
|
||||
this.props.permission._links && this.props.permission._links.update ? (
|
||||
<td>
|
||||
<TypeSelector
|
||||
handleTypeChange={this.handleTypeChange}
|
||||
type={permission.type ? permission.type : "READ"}
|
||||
loading={loading}
|
||||
/>
|
||||
</td>
|
||||
) : (
|
||||
<td>{permission.type}</td>
|
||||
);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{permission.name}</td>
|
||||
<td>
|
||||
<Checkbox checked={permission ? permission.groupPermission : false} />
|
||||
</td>
|
||||
{typeSelector}
|
||||
<td>
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
namespace={namespace}
|
||||
repoName={repoName}
|
||||
deletePermission={this.deletePermission}
|
||||
loading={this.props.deleteLoading}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
handleTypeChange = (type: string) => {
|
||||
this.setState({
|
||||
permission: {
|
||||
...this.state.permission,
|
||||
type: type
|
||||
}
|
||||
});
|
||||
this.modifyPermission(type);
|
||||
};
|
||||
|
||||
modifyPermission = (type: string) => {
|
||||
let permission = this.state.permission;
|
||||
permission.type = type;
|
||||
this.props.modifyPermission(
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
createSelectOptions(types: string[]) {
|
||||
return types.map(type => {
|
||||
return {
|
||||
label: type,
|
||||
value: type
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const permission = ownProps.permission;
|
||||
const loading = isModifyPermissionPending(
|
||||
state,
|
||||
ownProps.namespace,
|
||||
ownProps.repoName,
|
||||
permission
|
||||
);
|
||||
const deleteLoading = isDeletePermissionPending(
|
||||
state,
|
||||
ownProps.namespace,
|
||||
ownProps.repoName,
|
||||
permission
|
||||
);
|
||||
|
||||
return { loading, deleteLoading };
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
modifyPermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
) => {
|
||||
dispatch(modifyPermission(permission, namespace, repoName));
|
||||
},
|
||||
deletePermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
) => {
|
||||
dispatch(deletePermission(permission, namespace, repoName));
|
||||
}
|
||||
};
|
||||
};
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(SinglePermission));
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type {
|
||||
AvailableRepositoryPermissions,
|
||||
Permission
|
||||
} from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
modifyPermission,
|
||||
isModifyPermissionPending,
|
||||
deletePermission,
|
||||
isDeletePermissionPending,
|
||||
findMatchingRoleName
|
||||
} from "../modules/permissions";
|
||||
import { connect } from "react-redux";
|
||||
import type { History } from "history";
|
||||
import { Button, Checkbox } from "@scm-manager/ui-components";
|
||||
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
|
||||
import RoleSelector from "../components/RoleSelector";
|
||||
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||
|
||||
type Props = {
|
||||
availablePermissions: AvailableRepositoryPermissions,
|
||||
submitForm: Permission => void,
|
||||
modifyPermission: (permission: Permission, namespace: string, name: string) => void,
|
||||
permission: Permission,
|
||||
t: string => string,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
match: any,
|
||||
history: History,
|
||||
loading: boolean,
|
||||
deletePermission: (permission: Permission, namespace: string, name: string) => void,
|
||||
deleteLoading: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
role: string,
|
||||
permission: Permission,
|
||||
showAdvancedDialog: boolean
|
||||
};
|
||||
|
||||
class SinglePermission extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const defaultPermission = props.availablePermissions.availableRoles
|
||||
? props.availablePermissions.availableRoles[0]
|
||||
: {};
|
||||
|
||||
this.state = {
|
||||
permission: {
|
||||
name: "",
|
||||
verbs: defaultPermission.verbs,
|
||||
groupPermission: false,
|
||||
_links: {}
|
||||
},
|
||||
role: defaultPermission.name,
|
||||
showAdvancedDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { availablePermissions, permission } = this.props;
|
||||
|
||||
const matchingRole = findMatchingRoleName(
|
||||
availablePermissions,
|
||||
permission.verbs
|
||||
);
|
||||
|
||||
if (permission) {
|
||||
this.setState({
|
||||
permission: {
|
||||
name: permission.name,
|
||||
verbs: permission.verbs,
|
||||
groupPermission: permission.groupPermission,
|
||||
_links: permission._links
|
||||
},
|
||||
role: matchingRole
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deletePermission = () => {
|
||||
this.props.deletePermission(
|
||||
this.props.permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { role, permission, showAdvancedDialog } = this.state;
|
||||
const {
|
||||
t,
|
||||
availablePermissions,
|
||||
loading,
|
||||
namespace,
|
||||
repoName
|
||||
} = this.props;
|
||||
const availableRoleNames = availablePermissions.availableRoles.map(
|
||||
r => r.name
|
||||
);
|
||||
const readOnly = !this.mayChangePermissions();
|
||||
const roleSelector = readOnly ? (
|
||||
<td>{role}</td>
|
||||
) : (
|
||||
<td>
|
||||
<RoleSelector
|
||||
handleRoleChange={this.handleRoleChange}
|
||||
availableRoles={availableRoleNames}
|
||||
role={role}
|
||||
loading={loading}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
|
||||
const advancedDialg = showAdvancedDialog ? (
|
||||
<AdvancedPermissionsDialog
|
||||
readOnly={readOnly}
|
||||
availableVerbs={availablePermissions.availableVerbs}
|
||||
selectedVerbs={permission.verbs}
|
||||
onClose={this.closeAdvancedPermissionsDialog}
|
||||
onSubmit={this.submitAdvancedPermissionsDialog}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{permission.name}</td>
|
||||
<td>
|
||||
<Checkbox
|
||||
checked={permission ? permission.groupPermission : false}
|
||||
disabled={true}
|
||||
/>
|
||||
</td>
|
||||
{roleSelector}
|
||||
<td>
|
||||
<Button
|
||||
label={t("permission.advanced-button.label")}
|
||||
action={this.handleDetailedPermissionsPressed}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
namespace={namespace}
|
||||
repoName={repoName}
|
||||
deletePermission={this.deletePermission}
|
||||
loading={this.props.deleteLoading}
|
||||
/>
|
||||
{advancedDialg}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
mayChangePermissions = () => {
|
||||
return this.props.permission._links && this.props.permission._links.update;
|
||||
};
|
||||
|
||||
handleDetailedPermissionsPressed = () => {
|
||||
this.setState({ showAdvancedDialog: true });
|
||||
};
|
||||
|
||||
closeAdvancedPermissionsDialog = () => {
|
||||
this.setState({ showAdvancedDialog: false });
|
||||
};
|
||||
|
||||
submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
|
||||
const { permission } = this.state;
|
||||
const newRole = findMatchingRoleName(
|
||||
this.props.availablePermissions,
|
||||
newVerbs
|
||||
);
|
||||
this.setState(
|
||||
{
|
||||
showAdvancedDialog: false,
|
||||
permission: { ...permission, verbs: newVerbs },
|
||||
role: newRole
|
||||
},
|
||||
() => this.modifyPermission(newVerbs)
|
||||
);
|
||||
};
|
||||
|
||||
handleRoleChange = (role: string) => {
|
||||
const selectedRole = this.findAvailableRole(role);
|
||||
this.setState(
|
||||
{
|
||||
permission: {
|
||||
...this.state.permission,
|
||||
verbs: selectedRole.verbs
|
||||
},
|
||||
role: role
|
||||
},
|
||||
() => this.modifyPermission(selectedRole.verbs)
|
||||
);
|
||||
};
|
||||
|
||||
findAvailableRole = (roleName: string) => {
|
||||
return this.props.availablePermissions.availableRoles.find(
|
||||
role => role.name === roleName
|
||||
);
|
||||
};
|
||||
|
||||
modifyPermission = (verbs: string[]) => {
|
||||
let permission = this.state.permission;
|
||||
permission.verbs = verbs;
|
||||
this.props.modifyPermission(
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const permission = ownProps.permission;
|
||||
const loading = isModifyPermissionPending(
|
||||
state,
|
||||
ownProps.namespace,
|
||||
ownProps.repoName,
|
||||
permission
|
||||
);
|
||||
const deleteLoading = isDeletePermissionPending(
|
||||
state,
|
||||
ownProps.namespace,
|
||||
ownProps.repoName,
|
||||
permission
|
||||
);
|
||||
|
||||
return { loading, deleteLoading };
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
modifyPermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
) => {
|
||||
dispatch(modifyPermission(permission, namespace, repoName));
|
||||
},
|
||||
deletePermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
) => {
|
||||
dispatch(deletePermission(permission, namespace, repoName));
|
||||
}
|
||||
};
|
||||
};
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(SinglePermission));
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Action } from "@scm-manager/ui-components";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import * as types from "../../../modules/types";
|
||||
import type {
|
||||
AvailableRepositoryPermissions,
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
@@ -11,7 +12,18 @@ import type {
|
||||
import { isPending } from "../../../modules/pending";
|
||||
import { getFailure } from "../../../modules/failure";
|
||||
import { Dispatch } from "redux";
|
||||
import { getLinks } from "../../../modules/indexResource";
|
||||
|
||||
export const FETCH_AVAILABLE = "scm/permissions/FETCH_AVAILABLE";
|
||||
export const FETCH_AVAILABLE_PENDING = `${FETCH_AVAILABLE}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const FETCH_AVAILABLE_SUCCESS = `${FETCH_AVAILABLE}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const FETCH_AVAILABLE_FAILURE = `${FETCH_AVAILABLE}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS";
|
||||
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
|
||||
types.PENDING_SUFFIX
|
||||
@@ -62,7 +74,71 @@ export const DELETE_PERMISSION_RESET = `${DELETE_PERMISSION}_${
|
||||
types.RESET_SUFFIX
|
||||
}`;
|
||||
|
||||
const CONTENT_TYPE = "application/vnd.scmm-permission+json";
|
||||
const CONTENT_TYPE = "application/vnd.scmm-repositoryPermission+json";
|
||||
|
||||
// fetch available permissions
|
||||
|
||||
export function fetchAvailablePermissionsIfNeeded() {
|
||||
return function(dispatch: any, getState: () => Object) {
|
||||
if (shouldFetchAvailablePermissions(getState())) {
|
||||
return fetchAvailablePermissions(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAvailablePermissions(
|
||||
dispatch: any,
|
||||
getState: () => Object
|
||||
) {
|
||||
dispatch(fetchAvailablePending());
|
||||
return apiClient
|
||||
.get(getLinks(getState()).availableRepositoryPermissions.href)
|
||||
.then(response => response.json())
|
||||
.then(available => {
|
||||
dispatch(fetchAvailableSuccess(available));
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(fetchAvailableFailure(err));
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldFetchAvailablePermissions(state: Object) {
|
||||
if (
|
||||
isFetchAvailablePermissionsPending(state) ||
|
||||
getFetchAvailablePermissionsFailure(state)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return !state.available;
|
||||
}
|
||||
|
||||
export function fetchAvailablePending(): Action {
|
||||
return {
|
||||
type: FETCH_AVAILABLE_PENDING,
|
||||
payload: {},
|
||||
itemId: "available"
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAvailableSuccess(
|
||||
available: AvailableRepositoryPermissions
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_AVAILABLE_SUCCESS,
|
||||
payload: available,
|
||||
itemId: "available"
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAvailableFailure(error: Error): Action {
|
||||
return {
|
||||
type: FETCH_AVAILABLE_FAILURE,
|
||||
payload: {
|
||||
error
|
||||
},
|
||||
itemId: "available"
|
||||
};
|
||||
}
|
||||
|
||||
// fetch permissions
|
||||
|
||||
@@ -368,6 +444,7 @@ export function deletePermissionReset(namespace: string, repoName: string) {
|
||||
itemId: namespace + "/" + repoName
|
||||
};
|
||||
}
|
||||
|
||||
function deletePermissionFromState(
|
||||
oldPermissions: PermissionCollection,
|
||||
permission: Permission
|
||||
@@ -399,12 +476,17 @@ export default function reducer(
|
||||
return state;
|
||||
}
|
||||
switch (action.type) {
|
||||
case FETCH_AVAILABLE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
available: action.payload
|
||||
};
|
||||
case FETCH_PERMISSIONS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
[action.itemId]: {
|
||||
entries: action.payload._embedded.permissions,
|
||||
createPermission: action.payload._links.create ? true : false
|
||||
createPermission: !!action.payload._links.create
|
||||
}
|
||||
};
|
||||
case MODIFY_PERMISSION_SUCCESS:
|
||||
@@ -452,6 +534,12 @@ export default function reducer(
|
||||
|
||||
// selectors
|
||||
|
||||
export function getAvailablePermissions(state: Object) {
|
||||
if (state.permissions) {
|
||||
return state.permissions.available;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPermissionsOfRepo(
|
||||
state: Object,
|
||||
namespace: string,
|
||||
@@ -463,6 +551,10 @@ export function getPermissionsOfRepo(
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchAvailablePermissionsPending(state: Object) {
|
||||
return isPending(state, FETCH_AVAILABLE, "available");
|
||||
}
|
||||
|
||||
export function isFetchPermissionsPending(
|
||||
state: Object,
|
||||
namespace: string,
|
||||
@@ -471,6 +563,10 @@ export function isFetchPermissionsPending(
|
||||
return isPending(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
||||
}
|
||||
|
||||
export function getFetchAvailablePermissionsFailure(state: Object) {
|
||||
return getFailure(state, FETCH_AVAILABLE, "available");
|
||||
}
|
||||
|
||||
export function getFetchPermissionsFailure(
|
||||
state: Object,
|
||||
namespace: string,
|
||||
@@ -522,6 +618,7 @@ export function isCreatePermissionPending(
|
||||
) {
|
||||
return isPending(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
||||
}
|
||||
|
||||
export function getCreatePermissionFailure(
|
||||
state: Object,
|
||||
namespace: string,
|
||||
@@ -603,3 +700,33 @@ export function getModifyPermissionsFailure(
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findMatchingRoleName(
|
||||
availablePermissions: AvailableRepositoryPermissions,
|
||||
verbs: string[]
|
||||
) {
|
||||
if (!verbs) {
|
||||
return "";
|
||||
}
|
||||
const matchingRole = availablePermissions.availableRoles.find(role => {
|
||||
return equalVerbs(role.verbs, verbs);
|
||||
});
|
||||
|
||||
if (matchingRole) {
|
||||
return matchingRole.name;
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ const hitchhiker_puzzle42Permission_user_eins: Permission = {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||
}
|
||||
}
|
||||
},
|
||||
verbs: []
|
||||
};
|
||||
|
||||
const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
||||
@@ -79,7 +80,8 @@ const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||
}
|
||||
}
|
||||
},
|
||||
verbs: []
|
||||
};
|
||||
|
||||
const hitchhiker_puzzle42Permissions: PermissionCollection = [
|
||||
@@ -175,8 +177,7 @@ describe("permission fetch", () => {
|
||||
}
|
||||
);
|
||||
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||
editedPermission.type = "OWNER";
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
@@ -197,8 +198,7 @@ describe("permission fetch", () => {
|
||||
}
|
||||
);
|
||||
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||
editedPermission.type = "OWNER";
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
@@ -227,8 +227,7 @@ describe("permission fetch", () => {
|
||||
}
|
||||
);
|
||||
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||
editedPermission.type = "OWNER";
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
@@ -451,8 +450,7 @@ describe("permissions reducer", () => {
|
||||
entries: [hitchhiker_puzzle42Permission_user_eins]
|
||||
}
|
||||
};
|
||||
let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||
permissionEdited.type = "OWNER";
|
||||
let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
let expectedState = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [permissionEdited]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Button } from "@scm-manager/ui-components";
|
||||
import { ButtonGroup } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
showHistory: boolean => void
|
||||
};
|
||||
|
||||
class ButtonGroup extends React.Component<Props> {
|
||||
class FileButtonGroup extends React.Component<Props> {
|
||||
showHistory = () => {
|
||||
this.props.showHistory(true);
|
||||
};
|
||||
@@ -21,15 +21,6 @@ class ButtonGroup extends React.Component<Props> {
|
||||
render() {
|
||||
const { t, historyIsSelected } = this.props;
|
||||
|
||||
let sourcesColor = "";
|
||||
let historyColor = "";
|
||||
|
||||
if (historyIsSelected) {
|
||||
historyColor = "link is-selected";
|
||||
} else {
|
||||
sourcesColor = "link is-selected";
|
||||
}
|
||||
|
||||
const sourcesLabel = (
|
||||
<>
|
||||
<span className="icon">
|
||||
@@ -53,20 +44,15 @@ class ButtonGroup extends React.Component<Props> {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="buttons has-addons">
|
||||
<Button
|
||||
label={sourcesLabel}
|
||||
color={sourcesColor}
|
||||
action={this.showSources}
|
||||
/>
|
||||
<Button
|
||||
label={historyLabel}
|
||||
color={historyColor}
|
||||
action={this.showHistory}
|
||||
/>
|
||||
</div>
|
||||
<ButtonGroup
|
||||
firstlabel={sourcesLabel}
|
||||
secondlabel={historyLabel}
|
||||
firstAction={this.showSources}
|
||||
secondAction={this.showHistory}
|
||||
firstIsSelected={!historyIsSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(ButtonGroup);
|
||||
export default translate("repos")(FileButtonGroup);
|
||||
@@ -6,7 +6,7 @@ import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import FileSize from "../components/FileSize";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
import ButtonGroup from "../components/content/ButtonGroup";
|
||||
import FileButtonGroup from "../components/content/FileButtonGroup";
|
||||
import SourcesView from "./SourcesView";
|
||||
import HistoryView from "./HistoryView";
|
||||
import { getSources } from "../modules/sources";
|
||||
@@ -76,7 +76,7 @@ class Content extends React.Component<Props, State> {
|
||||
const icon = collapsed ? "fa-angle-right" : "fa-angle-down";
|
||||
|
||||
const selector = file._links.history ? (
|
||||
<ButtonGroup
|
||||
<FileButtonGroup
|
||||
file={file}
|
||||
historyIsSelected={showHistory}
|
||||
showHistory={(changeShowHistory: boolean) =>
|
||||
|
||||
Reference in New Issue
Block a user