mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
merge
This commit is contained in:
@@ -4,11 +4,11 @@ import java.net.URI;
|
|||||||
|
|
||||||
public interface ScmPathInfo {
|
public interface ScmPathInfo {
|
||||||
|
|
||||||
String REST_API_PATH = "/api/rest";
|
String REST_API_PATH = "/api";
|
||||||
|
|
||||||
URI getApiRestUri();
|
URI getApiRestUri();
|
||||||
|
|
||||||
default URI getRootUri() {
|
default URI getRootUri() {
|
||||||
return getApiRestUri().resolve("../..");
|
return getApiRestUri().resolve("..");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public class InitializingHttpScmProtocolWrapperTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
|
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
|
||||||
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/rest/"));
|
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ package sonia.scm.it;
|
|||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import static java.net.URI.create;
|
|||||||
public class RestUtil {
|
public class RestUtil {
|
||||||
|
|
||||||
public static final URI BASE_URL = create("http://localhost:8081/scm/");
|
public static final URI BASE_URL = create("http://localhost:8081/scm/");
|
||||||
public static final URI REST_BASE_URL = BASE_URL.resolve("api/rest/v2/");
|
public static final URI REST_BASE_URL = BASE_URL.resolve("api/v2/");
|
||||||
|
|
||||||
public static URI createResourceUrl(String path) {
|
public static URI createResourceUrl(String path) {
|
||||||
return REST_BASE_URL.resolve(path);
|
return REST_BASE_URL.resolve(path);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"link": "lerna exec -- yarn link",
|
"link": "lerna exec -- yarn link",
|
||||||
"unlink": "lerna exec -- yarn unlink"
|
"unlink": "lerna exec --no-bail -- yarn unlink"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"lerna": "^3.2.1"
|
"lerna": "^3.2.1"
|
||||||
|
|||||||
39
scm-ui-components/packages/ui-components/src/Help.js
Normal file
39
scm-ui-components/packages/ui-components/src/Help.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
img: {
|
||||||
|
display: "block"
|
||||||
|
},
|
||||||
|
q: {
|
||||||
|
float: "left",
|
||||||
|
paddingLeft: "3px",
|
||||||
|
float: "right"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
message: string,
|
||||||
|
classes: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class Help extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { message, classes } = this.props;
|
||||||
|
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames("tooltip is-tooltip-right", multiline, classes.q)}
|
||||||
|
data-tooltip={message}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className={classNames("fa fa-question has-text-info", classes.img)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(Help);
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { Help } from "./index";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string,
|
||||||
|
helpText?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
class LabelWithHelpIcon extends React.Component<Props> {
|
||||||
|
renderLabel = () => {
|
||||||
|
const label = this.props.label;
|
||||||
|
if (label) {
|
||||||
|
return <label className="label">{label}</label>;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHelp = () => {
|
||||||
|
const helpText = this.props.helpText;
|
||||||
|
if (helpText) {
|
||||||
|
return (
|
||||||
|
<div className="control columns is-vcentered">
|
||||||
|
<Help message={helpText} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderLabelWithHelpIcon = () => {
|
||||||
|
if (this.props.label) {
|
||||||
|
return (
|
||||||
|
<div className="field is-grouped">
|
||||||
|
<div className="control">{this.renderLabel()}</div>
|
||||||
|
{this.renderHelp()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.renderLabelWithHelpIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LabelWithHelpIcon;
|
||||||
@@ -32,7 +32,7 @@ export function createUrl(url: string) {
|
|||||||
if (url.indexOf("/") !== 0) {
|
if (url.indexOf("/") !== 0) {
|
||||||
urlWithStartingSlash = "/" + urlWithStartingSlash;
|
urlWithStartingSlash = "/" + urlWithStartingSlash;
|
||||||
}
|
}
|
||||||
return `${contextPath}/api/rest/v2${urlWithStartingSlash}`;
|
return `${contextPath}/api/v2${urlWithStartingSlash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe("create url", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should add prefix for api", () => {
|
it("should add prefix for api", () => {
|
||||||
expect(createUrl("/users")).toBe("/api/rest/v2/users");
|
expect(createUrl("/users")).toBe("/api/v2/users");
|
||||||
expect(createUrl("users")).toBe("/api/rest/v2/users");
|
expect(createUrl("users")).toBe("/api/v2/users");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ type Props = {
|
|||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
buttonLabel: string,
|
buttonLabel: string,
|
||||||
fieldLabel: string,
|
fieldLabel: string,
|
||||||
errorMessage: string
|
errorMessage: string,
|
||||||
|
helpText?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -25,7 +26,13 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { disabled, buttonLabel, fieldLabel, errorMessage } = this.props;
|
const {
|
||||||
|
disabled,
|
||||||
|
buttonLabel,
|
||||||
|
fieldLabel,
|
||||||
|
errorMessage,
|
||||||
|
helpText
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<InputField
|
<InputField
|
||||||
@@ -36,6 +43,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
|||||||
value={this.state.entryToAdd}
|
value={this.state.entryToAdd}
|
||||||
onReturnPressed={this.appendEntry}
|
onReturnPressed={this.appendEntry}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
helpText={helpText}
|
||||||
/>
|
/>
|
||||||
<AddButton
|
<AddButton
|
||||||
label={buttonLabel}
|
label={buttonLabel}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Help } from "../index";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string,
|
label?: string,
|
||||||
checked: boolean,
|
checked: boolean,
|
||||||
onChange?: boolean => void,
|
onChange?: boolean => void,
|
||||||
disabled?: boolean
|
disabled?: boolean,
|
||||||
|
helpText?: string
|
||||||
};
|
};
|
||||||
class Checkbox extends React.Component<Props> {
|
class Checkbox extends React.Component<Props> {
|
||||||
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
@@ -14,9 +16,20 @@ class Checkbox extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderHelp = () => {
|
||||||
|
const helpText = this.props.helpText;
|
||||||
|
if (helpText) {
|
||||||
|
return (
|
||||||
|
<div className="control columns is-vcentered">
|
||||||
|
<Help message={helpText} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field is-grouped">
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<label className="checkbox" disabled={this.props.disabled}>
|
<label className="checkbox" disabled={this.props.disabled}>
|
||||||
<input
|
<input
|
||||||
@@ -28,6 +41,7 @@ class Checkbox extends React.Component<Props> {
|
|||||||
{this.props.label}
|
{this.props.label}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{this.renderHelp()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { LabelWithHelpIcon } from "../index";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string,
|
label?: string,
|
||||||
@@ -12,7 +13,8 @@ type Props = {
|
|||||||
onReturnPressed?: () => void,
|
onReturnPressed?: () => void,
|
||||||
validationError: boolean,
|
validationError: boolean,
|
||||||
errorMessage: string,
|
errorMessage: string,
|
||||||
disabled?: boolean
|
disabled?: boolean,
|
||||||
|
helpText?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class InputField extends React.Component<Props> {
|
class InputField extends React.Component<Props> {
|
||||||
@@ -33,15 +35,6 @@ class InputField extends React.Component<Props> {
|
|||||||
this.props.onChange(event.target.value);
|
this.props.onChange(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderLabel = () => {
|
|
||||||
const label = this.props.label;
|
|
||||||
if (label) {
|
|
||||||
return <label className="label">{label}</label>;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
handleKeyPress = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
|
handleKeyPress = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
|
||||||
const onReturnPressed = this.props.onReturnPressed;
|
const onReturnPressed = this.props.onReturnPressed;
|
||||||
if (!onReturnPressed) {
|
if (!onReturnPressed) {
|
||||||
@@ -60,7 +53,9 @@ class InputField extends React.Component<Props> {
|
|||||||
value,
|
value,
|
||||||
validationError,
|
validationError,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
disabled
|
disabled,
|
||||||
|
label,
|
||||||
|
helpText
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const errorView = validationError ? "is-danger" : "";
|
const errorView = validationError ? "is-danger" : "";
|
||||||
const helper = validationError ? (
|
const helper = validationError ? (
|
||||||
@@ -70,7 +65,7 @@ class InputField extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field">
|
||||||
{this.renderLabel()}
|
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<input
|
<input
|
||||||
ref={input => {
|
ref={input => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { LabelWithHelpIcon } from "../index";
|
||||||
|
|
||||||
export type SelectItem = {
|
export type SelectItem = {
|
||||||
value: string,
|
value: string,
|
||||||
@@ -10,7 +12,9 @@ type Props = {
|
|||||||
label?: string,
|
label?: string,
|
||||||
options: SelectItem[],
|
options: SelectItem[],
|
||||||
value?: SelectItem,
|
value?: SelectItem,
|
||||||
onChange: string => void
|
onChange: string => void,
|
||||||
|
loading?: boolean,
|
||||||
|
helpText?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class Select extends React.Component<Props> {
|
class Select extends React.Component<Props> {
|
||||||
@@ -28,21 +32,18 @@ class Select extends React.Component<Props> {
|
|||||||
this.props.onChange(event.target.value);
|
this.props.onChange(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderLabel = () => {
|
|
||||||
const label = this.props.label;
|
|
||||||
if (label) {
|
|
||||||
return <label className="label">{label}</label>;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { options, value } = this.props;
|
const { options, value, label, helpText, loading } = this.props;
|
||||||
|
const loadingClass = loading ? "is-loading" : "";
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field">
|
||||||
{this.renderLabel()}
|
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||||
<div className="control select">
|
<div className={classNames(
|
||||||
|
"control select",
|
||||||
|
loadingClass
|
||||||
|
)}>
|
||||||
<select
|
<select
|
||||||
ref={input => {
|
ref={input => {
|
||||||
this.field = input;
|
this.field = input;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { LabelWithHelpIcon } from "../index";
|
||||||
|
|
||||||
export type SelectItem = {
|
export type SelectItem = {
|
||||||
value: string,
|
value: string,
|
||||||
@@ -10,7 +11,8 @@ type Props = {
|
|||||||
label?: string,
|
label?: string,
|
||||||
placeholder?: SelectItem[],
|
placeholder?: SelectItem[],
|
||||||
value?: string,
|
value?: string,
|
||||||
onChange: string => void
|
onChange: string => void,
|
||||||
|
helpText?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class Textarea extends React.Component<Props> {
|
class Textarea extends React.Component<Props> {
|
||||||
@@ -20,20 +22,12 @@ class Textarea extends React.Component<Props> {
|
|||||||
this.props.onChange(event.target.value);
|
this.props.onChange(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderLabel = () => {
|
|
||||||
const label = this.props.label;
|
|
||||||
if (label) {
|
|
||||||
return <label className="label">{label}</label>;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { placeholder, value } = this.props;
|
const { placeholder, value, label, helpText } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field">
|
||||||
{this.renderLabel()}
|
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<textarea
|
<textarea
|
||||||
className="textarea"
|
className="textarea"
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ export { default as MailLink } from "./MailLink.js";
|
|||||||
export { default as Notification } from "./Notification.js";
|
export { default as Notification } from "./Notification.js";
|
||||||
export { default as Paginator } from "./Paginator.js";
|
export { default as Paginator } from "./Paginator.js";
|
||||||
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
||||||
|
export { default as Help } from "./Help.js";
|
||||||
|
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
||||||
|
|
||||||
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export * from "./buttons";
|
export * from "./buttons";
|
||||||
export * from "./forms";
|
export * from "./forms";
|
||||||
export * from "./layout";
|
export * from "./layout";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
const nameRegex = /^([A-z0-9.\-_@]|[^ ]([A-z0-9.\-_@ ]*[A-z0-9.\-_@]|[^\s])?)$/;
|
const nameRegex = /^[A-Za-z0-9\.\-_][A-Za-z0-9\.\-_@]*$/;
|
||||||
|
|
||||||
export const isNameValid = (name: string) => {
|
export const isNameValid = (name: string) => {
|
||||||
return nameRegex.test(name);
|
return nameRegex.test(name);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ describe("test name validation", () => {
|
|||||||
it("should return false", () => {
|
it("should return false", () => {
|
||||||
// invalid names taken from ValidationUtilTest.java
|
// invalid names taken from ValidationUtilTest.java
|
||||||
const invalidNames = [
|
const invalidNames = [
|
||||||
|
"@test",
|
||||||
" test 123",
|
" test 123",
|
||||||
" test 123 ",
|
" test 123 ",
|
||||||
"test 123 ",
|
"test 123 ",
|
||||||
@@ -35,10 +36,9 @@ describe("test name validation", () => {
|
|||||||
"Test123-git",
|
"Test123-git",
|
||||||
"Test_user-123.git",
|
"Test_user-123.git",
|
||||||
"test@scm-manager.de",
|
"test@scm-manager.de",
|
||||||
"test 123",
|
"test123",
|
||||||
"tt",
|
"tt",
|
||||||
"t",
|
"t",
|
||||||
|
|
||||||
"valid_name",
|
"valid_name",
|
||||||
"another1",
|
"another1",
|
||||||
"stillValid",
|
"stillValid",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
|||||||
|
//@flow
|
||||||
|
import type { Links } from "./hal";
|
||||||
|
|
||||||
|
export type Permission = {
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
groupPermission: boolean,
|
||||||
|
_links?: Links
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PermissionEntry = {
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
groupPermission: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PermissionCollection = Permission[];
|
||||||
@@ -10,3 +10,5 @@ export type { Repository, RepositoryCollection, RepositoryGroup } from "./Reposi
|
|||||||
export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
|
export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
|
||||||
|
|
||||||
export type { Config } from "./Config";
|
export type { Config } from "./Config";
|
||||||
|
|
||||||
|
export type { Permission, PermissionEntry, PermissionCollection } from "./RepositoryPermissions";
|
||||||
|
|||||||
@@ -48,6 +48,16 @@
|
|||||||
<script>bootstrap</script>
|
<script>bootstrap</script>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>unlink</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<script>unlink</script>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>link</id>
|
<id>link</id>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@@ -58,16 +68,6 @@
|
|||||||
<script>link</script>
|
<script>link</script>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
|
||||||
<id>unlink</id>
|
|
||||||
<phase>clean</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>run</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<script>unlink</script>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^5.3.1",
|
"@fortawesome/fontawesome-free": "^5.3.1",
|
||||||
"@scm-manager/ui-extensions": "^0.0.7",
|
"@scm-manager/ui-extensions": "^0.0.7",
|
||||||
"bulma": "^0.7.1",
|
"bulma": "^0.7.1",
|
||||||
|
"bulma-tooltip": "^2.0.2",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
"i18next-browser-languagedetector": "^2.2.2",
|
"i18next-browser-languagedetector": "^2.2.2",
|
||||||
"i18next-fetch-backend": "^0.1.0",
|
"i18next-fetch-backend": "^0.1.0",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
|
"node-sass": "^4.9.3",
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.4.2",
|
||||||
"react-i18next": "^7.9.0",
|
"react-i18next": "^7.9.0",
|
||||||
|
|||||||
@@ -64,5 +64,29 @@
|
|||||||
"login-attempt-limit-timeout-invalid": "This is not a number",
|
"login-attempt-limit-timeout-invalid": "This is not a number",
|
||||||
"login-attempt-limit-invalid": "This is not a number",
|
"login-attempt-limit-invalid": "This is not a number",
|
||||||
"plugin-url-invalid": "This is not a valid url"
|
"plugin-url-invalid": "This is not a valid url"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"realmDescriptionHelpText": "Enter authentication realm description",
|
||||||
|
"dateFormatHelpText": "Moments date format. Please have a look at the momentjs documentation.",
|
||||||
|
"pluginRepositoryHelpText": "The url of the plugin repository. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture",
|
||||||
|
"enableForwardingHelpText": "Enbale mod_proxy port forwarding.",
|
||||||
|
"enableRepositoryArchiveHelpText": "Enable repository archives. A complete page reload is required after a change of this value.",
|
||||||
|
"disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.",
|
||||||
|
"allowAnonymousAccessHelpText": "Anonymous users have read access on public repositories.",
|
||||||
|
"skipFailedAuthenticatorsHelpText": "Do not stop the authentication chain, if an authenticator finds the user but fails to authenticate the user.",
|
||||||
|
"adminGroupsHelpText": "Names of groups with admin permissions.",
|
||||||
|
"adminUsersHelpText": "Names of users with admin permissions.",
|
||||||
|
"forceBaseUrlHelpText": "Redirects to the base url if the request comes from a other url",
|
||||||
|
"baseUrlHelpText": "The url of the application (with context path), i.e. http://localhost:8080/scm",
|
||||||
|
"loginAttemptLimitHelpText": "Maximum allowed login attempts. Use -1 to disable the login attempt limit.",
|
||||||
|
"loginAttemptLimitTimeoutHelpText": "Timeout in seconds for users which are temporary disabled, because of too many failed login attempts.",
|
||||||
|
"enableProxyHelpText": "Enable Proxy",
|
||||||
|
"proxyPortHelpText": "The proxy port",
|
||||||
|
"proxyPasswordHelpText": "The password for the proxy server authentication.",
|
||||||
|
"proxyServerHelpText": "The proxy server",
|
||||||
|
"proxyUserHelpText": "The username for the proxy server authentication.",
|
||||||
|
"proxyExcludesHelpText": "Glob patterns for hostnames which should be excluded from proxy settings.",
|
||||||
|
"enableXsrfProtectionHelpText": "Enable Xsrf Cookie Protection. Note: This feature is still experimental.",
|
||||||
|
"defaultNameSpaceStrategyHelpText": "The default namespace strategy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,12 @@
|
|||||||
"group-form": {
|
"group-form": {
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"name-error": "Group name is invalid",
|
"name-error": "Group name is invalid",
|
||||||
"description-error": "Description is invalid"
|
"description-error": "Description is invalid",
|
||||||
|
"help": {
|
||||||
|
"nameHelpText": "Unique name of the group",
|
||||||
|
"descriptionHelpText": "A short description of the group",
|
||||||
|
"memberHelpText": "Usernames of the group members"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"delete-group-button": {
|
"delete-group-button": {
|
||||||
"label": "Delete",
|
"label": "Delete",
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
"actions-label": "Actions",
|
"actions-label": "Actions",
|
||||||
"back-label": "Back",
|
"back-label": "Back",
|
||||||
"navigation-label": "Navigation",
|
"navigation-label": "Navigation",
|
||||||
"information": "Information"
|
"information": "Information",
|
||||||
|
"permissions": "Permissions"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Create Repository",
|
"title": "Create Repository",
|
||||||
@@ -42,5 +43,36 @@
|
|||||||
"submit": "Yes",
|
"submit": "Yes",
|
||||||
"cancel": "No"
|
"cancel": "No"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"error-title": "Error",
|
||||||
|
"error-subtitle": "Unknown permissions error",
|
||||||
|
"name": "User or Group",
|
||||||
|
"type": "Type",
|
||||||
|
"group-permission": "Group Permission",
|
||||||
|
"edit-permission": {
|
||||||
|
"delete-button": "Delete",
|
||||||
|
"save-button": "Save Changes"
|
||||||
|
},
|
||||||
|
"delete-permission-button": {
|
||||||
|
"label": "Delete",
|
||||||
|
"confirm-alert": {
|
||||||
|
"title": "Delete permission",
|
||||||
|
"message": "Do you really want to delete the permission?",
|
||||||
|
"submit": "Yes",
|
||||||
|
"cancel": "No"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"add-permission": {
|
||||||
|
"add-permission-heading": "Add new Permission",
|
||||||
|
"submit-button": "Submit",
|
||||||
|
"name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
||||||
|
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
||||||
|
"contactHelpText": "Email address of the person who is responsible for this repository.",
|
||||||
|
"descriptionHelpText": "A short description of the repository."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,5 +51,14 @@
|
|||||||
"password-invalid": "Password has to be between 6 and 32 characters",
|
"password-invalid": "Password has to be between 6 and 32 characters",
|
||||||
"passwordValidation-invalid": "Passwords have to be the same",
|
"passwordValidation-invalid": "Passwords have to be the same",
|
||||||
"validatePassword": "Please validate password here"
|
"validatePassword": "Please validate password here"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"usernameHelpText": "Unique name of the user.",
|
||||||
|
"displayNameHelpText": "Display name of the user.",
|
||||||
|
"mailHelpText": "Email address of the user.",
|
||||||
|
"passwordHelpText": "Plain text password of the user.",
|
||||||
|
"passwordConfirmHelpText": "Repeat the password for validation.",
|
||||||
|
"adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.",
|
||||||
|
"activeHelpText": "Activate or deactive the user."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ class BaseUrlSettings extends React.Component<Props> {
|
|||||||
label={t("base-url-settings.force-base-url")}
|
label={t("base-url-settings.force-base-url")}
|
||||||
onChange={this.handleForceBaseUrlChange}
|
onChange={this.handleForceBaseUrlChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.forceBaseUrlHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("base-url-settings.base-url")}
|
label={t("base-url-settings.base-url")}
|
||||||
onChange={this.handleBaseUrlChange}
|
onChange={this.handleBaseUrlChange}
|
||||||
value={baseUrl}
|
value={baseUrl}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.baseUrlHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,54 +41,63 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
onChange={this.handleRealmDescriptionChange}
|
onChange={this.handleRealmDescriptionChange}
|
||||||
value={realmDescription}
|
value={realmDescription}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.realmDescriptionHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("general-settings.date-format")}
|
label={t("general-settings.date-format")}
|
||||||
onChange={this.handleDateFormatChange}
|
onChange={this.handleDateFormatChange}
|
||||||
value={dateFormat}
|
value={dateFormat}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.dateFormatHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("general-settings.plugin-url")}
|
label={t("general-settings.plugin-url")}
|
||||||
onChange={this.handlePluginUrlChange}
|
onChange={this.handlePluginUrlChange}
|
||||||
value={pluginUrl}
|
value={pluginUrl}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.pluginRepositoryHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("general-settings.default-namespace-strategy")}
|
label={t("general-settings.default-namespace-strategy")}
|
||||||
onChange={this.handleDefaultNamespaceStrategyChange}
|
onChange={this.handleDefaultNamespaceStrategyChange}
|
||||||
value={defaultNamespaceStrategy}
|
value={defaultNamespaceStrategy}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.defaultNameSpaceStrategyHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={enabledXsrfProtection}
|
checked={enabledXsrfProtection}
|
||||||
label={t("general-settings.enabled-xsrf-protection")}
|
label={t("general-settings.enabled-xsrf-protection")}
|
||||||
onChange={this.handleEnabledXsrfProtectionChange}
|
onChange={this.handleEnabledXsrfProtectionChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.enableXsrfProtectionHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={enableRepositoryArchive}
|
checked={enableRepositoryArchive}
|
||||||
label={t("general-settings.enable-repository-archive")}
|
label={t("general-settings.enable-repository-archive")}
|
||||||
onChange={this.handleEnableRepositoryArchiveChange}
|
onChange={this.handleEnableRepositoryArchiveChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.enableRepositoryArchiveHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={disableGroupingGrid}
|
checked={disableGroupingGrid}
|
||||||
label={t("general-settings.disable-grouping-grid")}
|
label={t("general-settings.disable-grouping-grid")}
|
||||||
onChange={this.handleDisableGroupingGridChange}
|
onChange={this.handleDisableGroupingGridChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.disableGroupingGridHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={anonymousAccessEnabled}
|
checked={anonymousAccessEnabled}
|
||||||
label={t("general-settings.anonymous-access-enabled")}
|
label={t("general-settings.anonymous-access-enabled")}
|
||||||
onChange={this.handleAnonymousAccessEnabledChange}
|
onChange={this.handleAnonymousAccessEnabledChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.allowAnonymousAccessHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={skipFailedAuthenticators}
|
checked={skipFailedAuthenticators}
|
||||||
label={t("general-settings.skip-failed-authenticators")}
|
label={t("general-settings.skip-failed-authenticators")}
|
||||||
onChange={this.handleSkipFailedAuthenticatorsChange}
|
onChange={this.handleSkipFailedAuthenticatorsChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.skipFailedAuthenticatorsHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class LoginAttempt extends React.Component<Props, State> {
|
|||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
validationError={this.state.loginAttemptLimitError}
|
validationError={this.state.loginAttemptLimitError}
|
||||||
errorMessage={t("validation.login-attempt-limit-invalid")}
|
errorMessage={t("validation.login-attempt-limit-invalid")}
|
||||||
|
helpText={t("help.loginAttemptLimitHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("login-attempt.login-attempt-limit-timeout")}
|
label={t("login-attempt.login-attempt-limit-timeout")}
|
||||||
@@ -55,6 +56,7 @@ class LoginAttempt extends React.Component<Props, State> {
|
|||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
validationError={this.state.loginAttemptLimitTimeoutError}
|
validationError={this.state.loginAttemptLimitTimeoutError}
|
||||||
errorMessage={t("validation.login-attempt-limit-timeout-invalid")}
|
errorMessage={t("validation.login-attempt-limit-timeout-invalid")}
|
||||||
|
helpText={t("help.loginAttemptLimitTimeoutHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class ProxySettings extends React.Component<Props> {
|
|||||||
label={t("proxy-settings.enable-proxy")}
|
label={t("proxy-settings.enable-proxy")}
|
||||||
onChange={this.handleEnableProxyChange}
|
onChange={this.handleEnableProxyChange}
|
||||||
disabled={!hasUpdatePermission}
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.enableProxyHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("proxy-settings.proxy-password")}
|
label={t("proxy-settings.proxy-password")}
|
||||||
@@ -49,24 +50,28 @@ class ProxySettings extends React.Component<Props> {
|
|||||||
value={proxyPassword}
|
value={proxyPassword}
|
||||||
type="password"
|
type="password"
|
||||||
disabled={!enableProxy || !hasUpdatePermission}
|
disabled={!enableProxy || !hasUpdatePermission}
|
||||||
|
helpText={t("help.proxyPasswordHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("proxy-settings.proxy-port")}
|
label={t("proxy-settings.proxy-port")}
|
||||||
value={proxyPort}
|
value={proxyPort}
|
||||||
onChange={this.handleProxyPortChange}
|
onChange={this.handleProxyPortChange}
|
||||||
disabled={!enableProxy || !hasUpdatePermission}
|
disabled={!enableProxy || !hasUpdatePermission}
|
||||||
|
helpText={t("help.proxyPortHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("proxy-settings.proxy-server")}
|
label={t("proxy-settings.proxy-server")}
|
||||||
value={proxyServer}
|
value={proxyServer}
|
||||||
onChange={this.handleProxyServerChange}
|
onChange={this.handleProxyServerChange}
|
||||||
disabled={!enableProxy || !hasUpdatePermission}
|
disabled={!enableProxy || !hasUpdatePermission}
|
||||||
|
helpText={t("help.proxyServerHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("proxy-settings.proxy-user")}
|
label={t("proxy-settings.proxy-user")}
|
||||||
value={proxyUser}
|
value={proxyUser}
|
||||||
onChange={this.handleProxyUserChange}
|
onChange={this.handleProxyUserChange}
|
||||||
disabled={!enableProxy || !hasUpdatePermission}
|
disabled={!enableProxy || !hasUpdatePermission}
|
||||||
|
helpText={t("help.proxyUserHelpText")}
|
||||||
/>
|
/>
|
||||||
<ProxyExcludesTable
|
<ProxyExcludesTable
|
||||||
proxyExcludes={proxyExcludes}
|
proxyExcludes={proxyExcludes}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class AdminGroupTable extends React.Component<Props, State> {
|
|||||||
removeLabel={t("admin-settings.remove-group-button")}
|
removeLabel={t("admin-settings.remove-group-button")}
|
||||||
onRemove={this.removeEntry}
|
onRemove={this.removeEntry}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
helpText={t("help.adminGroupsHelpText")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class AdminUserTable extends React.Component<Props> {
|
|||||||
removeLabel={t("admin-settings.remove-user-button")}
|
removeLabel={t("admin-settings.remove-user-button")}
|
||||||
onRemove={this.removeEntry}
|
onRemove={this.removeEntry}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
helpText={t("help.adminUsersHelpText")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { RemoveEntryOfTableButton } from "@scm-manager/ui-components";
|
import { RemoveEntryOfTableButton, LabelWithHelpIcon } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
items: string[],
|
items: string[],
|
||||||
label: string,
|
label: string,
|
||||||
removeLabel: string,
|
removeLabel: string,
|
||||||
onRemove: (string[], string) => void,
|
onRemove: (string[], string) => void,
|
||||||
disabled: boolean
|
disabled: boolean,
|
||||||
|
helpText: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class ArrayConfigTable extends React.Component<Props> {
|
class ArrayConfigTable extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { label, disabled, removeLabel, items } = this.props;
|
const { label, disabled, removeLabel, items, helpText } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="label">{label}</label>
|
<LabelWithHelpIcon label={label} helpText={helpText}/>
|
||||||
<table className="table is-hoverable is-fullwidth">
|
<table className="table is-hoverable is-fullwidth">
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map(item => {
|
{items.map(item => {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ProxyExcludesTable extends React.Component<Props, State> {
|
|||||||
removeLabel={t("proxy-settings.remove-proxy-exclude-button")}
|
removeLabel={t("proxy-settings.remove-proxy-exclude-button")}
|
||||||
onRemove={this.removeEntry}
|
onRemove={this.removeEntry}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
helpText={t("help.proxyExcludesHelpText")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import reducer, {
|
|||||||
getConfigUpdatePermission
|
getConfigUpdatePermission
|
||||||
} from "./config";
|
} from "./config";
|
||||||
|
|
||||||
const CONFIG_URL = "/api/rest/v2/config";
|
const CONFIG_URL = "/api/v2/config";
|
||||||
|
|
||||||
const error = new Error("You have an error!");
|
const error = new Error("You have an error!");
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ const config = {
|
|||||||
enabledXsrfProtection: true,
|
enabledXsrfProtection: true,
|
||||||
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
|
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
|
||||||
_links: {
|
_links: {
|
||||||
self: { href: "http://localhost:8081/api/rest/v2/config" },
|
self: { href: "http://localhost:8081/api/v2/config" },
|
||||||
update: { href: "http://localhost:8081/api/rest/v2/config" }
|
update: { href: "http://localhost:8081/api/v2/config" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ const configWithNullValues = {
|
|||||||
enabledXsrfProtection: true,
|
enabledXsrfProtection: true,
|
||||||
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
|
defaultNamespaceStrategy: "sonia.scm.repository.DefaultNamespaceStrategy",
|
||||||
_links: {
|
_links: {
|
||||||
self: { href: "http://localhost:8081/api/rest/v2/config" },
|
self: { href: "http://localhost:8081/api/v2/config" },
|
||||||
update: { href: "http://localhost:8081/api/rest/v2/config" }
|
update: { href: "http://localhost:8081/api/v2/config" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ describe("config fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should successfully modify config", () => {
|
it("should successfully modify config", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/config", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/config", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ describe("config fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call the callback after modifying config", () => {
|
it("should call the callback after modifying config", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/config", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/config", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ describe("config fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail modifying config on HTTP 500", () => {
|
it("should fail modifying config on HTTP 500", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/config", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/config", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import groups from "./groups/modules/groups";
|
|||||||
import auth from "./modules/auth";
|
import auth from "./modules/auth";
|
||||||
import pending from "./modules/pending";
|
import pending from "./modules/pending";
|
||||||
import failure from "./modules/failure";
|
import failure from "./modules/failure";
|
||||||
|
import permissions from "./repos/permissions/modules/permissions";
|
||||||
import config from "./config/modules/config";
|
import config from "./config/modules/config";
|
||||||
|
|
||||||
import type { BrowserHistory } from "history/createBrowserHistory";
|
import type { BrowserHistory } from "history/createBrowserHistory";
|
||||||
@@ -26,6 +27,7 @@ function createReduxStore(history: BrowserHistory) {
|
|||||||
users,
|
users,
|
||||||
repos,
|
repos,
|
||||||
repositoryTypes,
|
repositoryTypes,
|
||||||
|
permissions,
|
||||||
groups,
|
groups,
|
||||||
auth,
|
auth,
|
||||||
config
|
config
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
onChange={this.handleGroupNameChange}
|
onChange={this.handleGroupNameChange}
|
||||||
value={group.name}
|
value={group.name}
|
||||||
validationError={this.state.nameValidationError}
|
validationError={this.state.nameValidationError}
|
||||||
|
helpText={t("group-form.help.nameHelpText")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -93,6 +94,7 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
onChange={this.handleDescriptionChange}
|
onChange={this.handleDescriptionChange}
|
||||||
value={group.description}
|
value={group.description}
|
||||||
validationError={false}
|
validationError={false}
|
||||||
|
helpText={t("group-form.help.descriptionHelpText")}
|
||||||
/>
|
/>
|
||||||
<MemberNameTable
|
<MemberNameTable
|
||||||
members={this.state.group.members}
|
members={this.state.group.members}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { RemoveEntryOfTableButton } from "@scm-manager/ui-components";
|
import {
|
||||||
|
RemoveEntryOfTableButton,
|
||||||
|
LabelWithHelpIcon
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
members: string[],
|
members: string[],
|
||||||
@@ -16,7 +19,10 @@ class MemberNameTable extends React.Component<Props, State> {
|
|||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="label">{t("group.members")}</label>
|
<LabelWithHelpIcon
|
||||||
|
label={t("group.members")}
|
||||||
|
helpText={t("group-form.help.memberHelpText")}
|
||||||
|
/>
|
||||||
<table className="table is-hoverable is-fullwidth">
|
<table className="table is-hoverable is-fullwidth">
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.props.members.map(member => {
|
{this.props.members.map(member => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import reducer, {
|
|||||||
MODIFY_GROUP_SUCCESS,
|
MODIFY_GROUP_SUCCESS,
|
||||||
MODIFY_GROUP_FAILURE
|
MODIFY_GROUP_FAILURE
|
||||||
} from "./groups";
|
} from "./groups";
|
||||||
const GROUPS_URL = "/api/rest/v2/groups";
|
const GROUPS_URL = "/api/v2/groups";
|
||||||
|
|
||||||
const error = new Error("You have an error!");
|
const error = new Error("You have an error!");
|
||||||
|
|
||||||
@@ -57,13 +57,13 @@ const humanGroup = {
|
|||||||
members: ["userZaphod"],
|
members: ["userZaphod"],
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/groups/humanGroup"
|
href: "http://localhost:8081/api/v2/groups/humanGroup"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href: "http://localhost:8081/api/rest/v2/groups/humanGroup"
|
href: "http://localhost:8081/api/v2/groups/humanGroup"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href:"http://localhost:8081/api/rest/v2/groups/humanGroup"
|
href:"http://localhost:8081/api/v2/groups/humanGroup"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -72,7 +72,7 @@ const humanGroup = {
|
|||||||
name: "userZaphod",
|
name: "userZaphod",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/userZaphod"
|
href: "http://localhost:8081/api/v2/users/userZaphod"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,13 +89,13 @@ const emptyGroup = {
|
|||||||
members: [],
|
members: [],
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/groups/emptyGroup"
|
href: "http://localhost:8081/api/v2/groups/emptyGroup"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href: "http://localhost:8081/api/rest/v2/groups/emptyGroup"
|
href: "http://localhost:8081/api/v2/groups/emptyGroup"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href:"http://localhost:8081/api/rest/v2/groups/emptyGroup"
|
href:"http://localhost:8081/api/v2/groups/emptyGroup"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -108,16 +108,16 @@ const responseBody = {
|
|||||||
pageTotal: 1,
|
pageTotal: 1,
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:3000/api/rest/v2/groups/?page=0&pageSize=10"
|
href: "http://localhost:3000/api/v2/groups/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
first: {
|
first: {
|
||||||
href: "http://localhost:3000/api/rest/v2/groups/?page=0&pageSize=10"
|
href: "http://localhost:3000/api/v2/groups/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
last: {
|
last: {
|
||||||
href: "http://localhost:3000/api/rest/v2/groups/?page=0&pageSize=10"
|
href: "http://localhost:3000/api/v2/groups/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
href: "http://localhost:3000/api/rest/v2/groups/"
|
href: "http://localhost:3000/api/v2/groups/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -244,7 +244,7 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should successfully modify group", () => {
|
it("should successfully modify group", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/groups/humanGroup", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call the callback after modifying group", () => {
|
it("should call the callback after modifying group", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/groups/humanGroup", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail modifying group on HTTP 500", () => {
|
it("should fail modifying group on HTTP 500", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/groups/humanGroup", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should delete successfully group humanGroup", () => {
|
it("should delete successfully group humanGroup", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
|
fetchMock.deleteOnce("http://localhost:8081/api/v2/groups/humanGroup", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call the callback, after successful delete", () => {
|
it("should call the callback, after successful delete", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
|
fetchMock.deleteOnce("http://localhost:8081/api/v2/groups/humanGroup", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -324,7 +324,7 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to delete group humanGroup", () => {
|
it("should fail to delete group humanGroup", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/groups/humanGroup", {
|
fetchMock.deleteOnce("http://localhost:8081/api/v2/groups/humanGroup", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch login success and dispatch fetch me", () => {
|
it("should dispatch login success and dispatch fetch me", () => {
|
||||||
fetchMock.postOnce("/api/rest/v2/auth/access_token", {
|
fetchMock.postOnce("/api/v2/auth/access_token", {
|
||||||
body: {
|
body: {
|
||||||
cookie: true,
|
cookie: true,
|
||||||
grant_type: "password",
|
grant_type: "password",
|
||||||
@@ -88,7 +88,7 @@ describe("auth actions", () => {
|
|||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchMock.getOnce("/api/rest/v2/me", {
|
fetchMock.getOnce("/api/v2/me", {
|
||||||
body: me,
|
body: me,
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
});
|
});
|
||||||
@@ -106,7 +106,7 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch login failure", () => {
|
it("should dispatch login failure", () => {
|
||||||
fetchMock.postOnce("/api/rest/v2/auth/access_token", {
|
fetchMock.postOnce("/api/v2/auth/access_token", {
|
||||||
status: 400
|
status: 400
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch fetch me success", () => {
|
it("should dispatch fetch me success", () => {
|
||||||
fetchMock.getOnce("/api/rest/v2/me", {
|
fetchMock.getOnce("/api/v2/me", {
|
||||||
body: me,
|
body: me,
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
});
|
});
|
||||||
@@ -141,7 +141,7 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch fetch me failure", () => {
|
it("should dispatch fetch me failure", () => {
|
||||||
fetchMock.getOnce("/api/rest/v2/me", {
|
fetchMock.getOnce("/api/v2/me", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch fetch me unauthorized", () => {
|
it("should dispatch fetch me unauthorized", () => {
|
||||||
fetchMock.getOnce("/api/rest/v2/me", {
|
fetchMock.getOnce("/api/v2/me", {
|
||||||
status: 401
|
status: 401
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,11 +173,11 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch logout success", () => {
|
it("should dispatch logout success", () => {
|
||||||
fetchMock.deleteOnce("/api/rest/v2/auth/access_token", {
|
fetchMock.deleteOnce("/api/v2/auth/access_token", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchMock.getOnce("/api/rest/v2/me", {
|
fetchMock.getOnce("/api/v2/me", {
|
||||||
status: 401
|
status: 401
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch logout failure", () => {
|
it("should dispatch logout failure", () => {
|
||||||
fetchMock.deleteOnce("/api/rest/v2/auth/access_token", {
|
fetchMock.deleteOnce("/api/v2/auth/access_token", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ function extractIdentifierFromFailure(action: Action) {
|
|||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeAllEntriesOfIdentifierFromState(
|
||||||
|
state: Object,
|
||||||
|
payload: any,
|
||||||
|
identifier: string
|
||||||
|
) {
|
||||||
|
const newState = {};
|
||||||
|
for (let failureType in state) {
|
||||||
|
if (failureType !== identifier && !failureType.startsWith(identifier)) {
|
||||||
|
newState[failureType] = state[failureType];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
function removeFromState(state: Object, identifier: string) {
|
function removeFromState(state: Object, identifier: string) {
|
||||||
const newState = {};
|
const newState = {};
|
||||||
for (let failureType in state) {
|
for (let failureType in state) {
|
||||||
@@ -47,7 +61,9 @@ export default function reducer(
|
|||||||
if (action.itemId) {
|
if (action.itemId) {
|
||||||
identifier += "/" + action.itemId;
|
identifier += "/" + action.itemId;
|
||||||
}
|
}
|
||||||
return removeFromState(state, identifier);
|
if (action.payload)
|
||||||
|
return removeAllEntriesOfIdentifierFromState(state, action.payload, identifier);
|
||||||
|
else return removeFromState(state, identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -19,6 +19,20 @@ function removeFromState(state: Object, identifier: string) {
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeAllEntriesOfIdentifierFromState(
|
||||||
|
state: Object,
|
||||||
|
payload: any,
|
||||||
|
identifier: string
|
||||||
|
) {
|
||||||
|
const newState = {};
|
||||||
|
for (let childType in state) {
|
||||||
|
if (childType !== identifier && !childType.startsWith(identifier)) {
|
||||||
|
newState[childType] = state[childType];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
function extractIdentifierFromPending(action: Action) {
|
function extractIdentifierFromPending(action: Action) {
|
||||||
const type = action.type;
|
const type = action.type;
|
||||||
let identifier = type.substring(0, type.length - PENDING_SUFFIX.length);
|
let identifier = type.substring(0, type.length - PENDING_SUFFIX.length);
|
||||||
@@ -48,6 +62,9 @@ export default function reducer(
|
|||||||
if (action.itemId) {
|
if (action.itemId) {
|
||||||
identifier += "/" + action.itemId;
|
identifier += "/" + action.itemId;
|
||||||
}
|
}
|
||||||
|
if (action.payload)
|
||||||
|
return removeAllEntriesOfIdentifierFromState(state, action.payload, identifier);
|
||||||
|
else
|
||||||
return removeFromState(state, identifier);
|
return removeFromState(state, identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
scm-ui/src/repos/components/PermissionsNavLink.js
Normal file
28
scm-ui/src/repos/components/PermissionsNavLink.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { NavLink } from "@scm-manager/ui-components";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
permissionUrl: string,
|
||||||
|
t: string => string,
|
||||||
|
repository: Repository
|
||||||
|
};
|
||||||
|
|
||||||
|
class PermissionsNavLink extends React.Component<Props> {
|
||||||
|
hasPermissionsLink = () => {
|
||||||
|
return this.props.repository._links.permissions;
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
if (!this.hasPermissionsLink()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { permissionUrl, t } = this.props;
|
||||||
|
return (
|
||||||
|
<NavLink to={permissionUrl} label={t("repository-root.permissions")} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("repos")(PermissionsNavLink);
|
||||||
39
scm-ui/src/repos/components/PermissionsNavLink.test.js
Normal file
39
scm-ui/src/repos/components/PermissionsNavLink.test.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { mount, shallow } from "enzyme";
|
||||||
|
import "../../tests/enzyme";
|
||||||
|
import "../../tests/i18n";
|
||||||
|
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
||||||
|
import PermissionsNavLink from "./PermissionsNavLink";
|
||||||
|
import EditNavLink from "./EditNavLink";
|
||||||
|
|
||||||
|
describe("PermissionsNavLink", () => {
|
||||||
|
const options = new ReactRouterEnzymeContext();
|
||||||
|
|
||||||
|
it("should render nothing, if the modify link is missing", () => {
|
||||||
|
const repository = {
|
||||||
|
_links: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLink = shallow(
|
||||||
|
<PermissionsNavLink repository={repository} permissionUrl="" />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
|
expect(navLink.text()).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the navLink", () => {
|
||||||
|
const repository = {
|
||||||
|
_links: {
|
||||||
|
permissions: {
|
||||||
|
href: "/permissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLink = mount(
|
||||||
|
<PermissionsNavLink repository={repository} permissionUrl="" />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
|
expect(navLink.text()).toBe("repository-root.permissions");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -90,12 +90,14 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
value={repository ? repository.contact : ""}
|
value={repository ? repository.contact : ""}
|
||||||
validationError={this.state.contactValidationError}
|
validationError={this.state.contactValidationError}
|
||||||
errorMessage={t("validation.contact-invalid")}
|
errorMessage={t("validation.contact-invalid")}
|
||||||
|
helpText={t("help.contactHelpText")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
label={t("repository.description")}
|
label={t("repository.description")}
|
||||||
onChange={this.handleDescriptionChange}
|
onChange={this.handleDescriptionChange}
|
||||||
value={repository ? repository.description : ""}
|
value={repository ? repository.description : ""}
|
||||||
|
helpText={t("help.descriptionHelpText")}
|
||||||
/>
|
/>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
disabled={!this.isValid()}
|
disabled={!this.isValid()}
|
||||||
@@ -129,12 +131,14 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
value={repository ? repository.name : ""}
|
value={repository ? repository.name : ""}
|
||||||
validationError={this.state.nameValidationError}
|
validationError={this.state.nameValidationError}
|
||||||
errorMessage={t("validation.name-invalid")}
|
errorMessage={t("validation.name-invalid")}
|
||||||
|
helpText={t("help.nameHelpText")}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label={t("repository.type")}
|
label={t("repository.type")}
|
||||||
onChange={this.handleTypeChange}
|
onChange={this.handleTypeChange}
|
||||||
value={repository ? repository.type : ""}
|
value={repository ? repository.type : ""}
|
||||||
options={this.createSelectOptions(repositoryTypes)}
|
options={this.createSelectOptions(repositoryTypes)}
|
||||||
|
helpText={t("help.typeHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ import { translate } from "react-i18next";
|
|||||||
import RepositoryDetails from "../components/RepositoryDetails";
|
import RepositoryDetails from "../components/RepositoryDetails";
|
||||||
import DeleteNavAction from "../components/DeleteNavAction";
|
import DeleteNavAction from "../components/DeleteNavAction";
|
||||||
import Edit from "../containers/Edit";
|
import Edit from "../containers/Edit";
|
||||||
|
import Permissions from "../permissions/containers/Permissions";
|
||||||
|
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import EditNavLink from "../components/EditNavLink";
|
import EditNavLink from "../components/EditNavLink";
|
||||||
|
import PermissionsNavLink from "../components/PermissionsNavLink";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -101,11 +103,24 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
path={`${url}/edit`}
|
path={`${url}/edit`}
|
||||||
component={() => <Edit repository={repository} />}
|
component={() => <Edit repository={repository} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${url}/permissions`}
|
||||||
|
render={props => (
|
||||||
|
<Permissions
|
||||||
|
namespace={this.props.repository.namespace}
|
||||||
|
repoName={this.props.repository.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<Navigation>
|
<Navigation>
|
||||||
<Section label={t("repository-root.navigation-label")}>
|
<Section label={t("repository-root.navigation-label")}>
|
||||||
<NavLink to={url} label={t("repository-root.information")} />
|
<NavLink to={url} label={t("repository-root.information")} />
|
||||||
|
<PermissionsNavLink
|
||||||
|
permissionUrl={`${url}/permissions`}
|
||||||
|
repository={repository}
|
||||||
|
/>
|
||||||
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
||||||
</Section>
|
</Section>
|
||||||
<Section label={t("repository-root.actions-label")}>
|
<Section label={t("repository-root.actions-label")}>
|
||||||
|
|||||||
@@ -58,33 +58,33 @@ const hitchhikerPuzzle42: Repository = {
|
|||||||
type: "svn",
|
type: "svn",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42"
|
href: "http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42"
|
href: "http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42"
|
href: "http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42"
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/permissions/"
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/tags/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/tags/"
|
||||||
},
|
},
|
||||||
branches: {
|
branches: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/branches/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/branches/"
|
||||||
},
|
},
|
||||||
changesets: {
|
changesets: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/changesets/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/changesets/"
|
||||||
},
|
},
|
||||||
sources: {
|
sources: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/puzzle42/sources/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/sources/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -100,35 +100,35 @@ const hitchhikerRestatend: Repository = {
|
|||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend"
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/permissions/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/permissions/"
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/tags/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/tags/"
|
||||||
},
|
},
|
||||||
branches: {
|
branches: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/branches/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/branches/"
|
||||||
},
|
},
|
||||||
changesets: {
|
changesets: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/changesets/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/changesets/"
|
||||||
},
|
},
|
||||||
sources: {
|
sources: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/hitchhiker/restatend/sources/"
|
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/sources/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -142,32 +142,32 @@ const slartiFjords: Repository = {
|
|||||||
creationDate: "2018-07-31T08:59:05.653Z",
|
creationDate: "2018-07-31T08:59:05.653Z",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords"
|
href: "http://localhost:8081/api/v2/repositories/slarti/fjords"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords"
|
href: "http://localhost:8081/api/v2/repositories/slarti/fjords"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords"
|
href: "http://localhost:8081/api/v2/repositories/slarti/fjords"
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords/permissions/"
|
"http://localhost:8081/api/v2/repositories/slarti/fjords/permissions/"
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/slarti/fjords/tags/"
|
href: "http://localhost:8081/api/v2/repositories/slarti/fjords/tags/"
|
||||||
},
|
},
|
||||||
branches: {
|
branches: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords/branches/"
|
"http://localhost:8081/api/v2/repositories/slarti/fjords/branches/"
|
||||||
},
|
},
|
||||||
changesets: {
|
changesets: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords/changesets/"
|
"http://localhost:8081/api/v2/repositories/slarti/fjords/changesets/"
|
||||||
},
|
},
|
||||||
sources: {
|
sources: {
|
||||||
href:
|
href:
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords/sources/"
|
"http://localhost:8081/api/v2/repositories/slarti/fjords/sources/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -177,16 +177,16 @@ const repositoryCollection: RepositoryCollection = {
|
|||||||
pageTotal: 1,
|
pageTotal: 1,
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
|
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
first: {
|
first: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
|
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
last: {
|
last: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
|
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/"
|
href: "http://localhost:8081/api/v2/repositories/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -199,16 +199,16 @@ const repositoryCollectionWithNames: RepositoryCollection = {
|
|||||||
pageTotal: 1,
|
pageTotal: 1,
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
|
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
first: {
|
first: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
|
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
last: {
|
last: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/?page=0&pageSize=10"
|
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositories/"
|
href: "http://localhost:8081/api/v2/repositories/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -221,7 +221,7 @@ const repositoryCollectionWithNames: RepositoryCollection = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("repos fetch", () => {
|
describe("repos fetch", () => {
|
||||||
const REPOS_URL = "/api/rest/v2/repositories";
|
const REPOS_URL = "/api/v2/repositories";
|
||||||
const SORT = "sortBy=namespaceAndName";
|
const SORT = "sortBy=namespaceAndName";
|
||||||
const REPOS_URL_WITH_SORT = REPOS_URL + "?" + SORT;
|
const REPOS_URL_WITH_SORT = REPOS_URL + "?" + SORT;
|
||||||
const mockStore = configureMockStore([thunk]);
|
const mockStore = configureMockStore([thunk]);
|
||||||
@@ -293,7 +293,7 @@ describe("repos fetch", () => {
|
|||||||
|
|
||||||
it("should append sortby parameter and successfully fetch repos from link", () => {
|
it("should append sortby parameter and successfully fetch repos from link", () => {
|
||||||
fetchMock.getOnce(
|
fetchMock.getOnce(
|
||||||
"/api/rest/v2/repositories?one=1&sortBy=namespaceAndName",
|
"/api/v2/repositories?one=1&sortBy=namespaceAndName",
|
||||||
repositoryCollection
|
repositoryCollection
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -421,7 +421,7 @@ describe("repos fetch", () => {
|
|||||||
|
|
||||||
it("should successfully delete repo slarti/fjords", () => {
|
it("should successfully delete repo slarti/fjords", () => {
|
||||||
fetchMock.delete(
|
fetchMock.delete(
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords",
|
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||||
{
|
{
|
||||||
status: 204
|
status: 204
|
||||||
}
|
}
|
||||||
@@ -448,7 +448,7 @@ describe("repos fetch", () => {
|
|||||||
|
|
||||||
it("should successfully delete repo slarti/fjords and call the callback", () => {
|
it("should successfully delete repo slarti/fjords and call the callback", () => {
|
||||||
fetchMock.delete(
|
fetchMock.delete(
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords",
|
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||||
{
|
{
|
||||||
status: 204
|
status: 204
|
||||||
}
|
}
|
||||||
@@ -468,7 +468,7 @@ describe("repos fetch", () => {
|
|||||||
|
|
||||||
it("should disapatch failure on delete, if server returns status code 500", () => {
|
it("should disapatch failure on delete, if server returns status code 500", () => {
|
||||||
fetchMock.delete(
|
fetchMock.delete(
|
||||||
"http://localhost:8081/api/rest/v2/repositories/slarti/fjords",
|
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||||
{
|
{
|
||||||
status: 500
|
status: 500
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const git = {
|
|||||||
displayName: "Git",
|
displayName: "Git",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositoryTypes/git"
|
href: "http://localhost:8081/api/v2/repositoryTypes/git"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -32,7 +32,7 @@ const hg = {
|
|||||||
displayName: "Mercurial",
|
displayName: "Mercurial",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositoryTypes/hg"
|
href: "http://localhost:8081/api/v2/repositoryTypes/hg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -42,7 +42,7 @@ const svn = {
|
|||||||
displayName: "Subversion",
|
displayName: "Subversion",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositoryTypes/svn"
|
href: "http://localhost:8081/api/v2/repositoryTypes/svn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -53,7 +53,7 @@ const collection = {
|
|||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/repositoryTypes"
|
href: "http://localhost:8081/api/v2/repositoryTypes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -97,7 +97,7 @@ describe("repository types caching", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("repository types fetch", () => {
|
describe("repository types fetch", () => {
|
||||||
const URL = "/api/rest/v2/repositoryTypes";
|
const URL = "/api/v2/repositoryTypes";
|
||||||
const mockStore = configureMockStore([thunk]);
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
122
scm-ui/src/repos/permissions/components/CreatePermissionForm.js
Normal file
122
scm-ui/src/repos/permissions/components/CreatePermissionForm.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import { Checkbox, InputField, SubmitButton } from "@scm-manager/ui-components";
|
||||||
|
import TypeSelector from "./TypeSelector";
|
||||||
|
import type {
|
||||||
|
PermissionCollection,
|
||||||
|
PermissionEntry
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
|
import * as validator from "./permissionValidation";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
t: string => string,
|
||||||
|
createPermission: (permission: PermissionEntry) => void,
|
||||||
|
loading: boolean,
|
||||||
|
currentPermissions: PermissionCollection
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
groupPermission: boolean,
|
||||||
|
valid: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class CreatePermissionForm extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: "",
|
||||||
|
type: "READ",
|
||||||
|
groupPermission: false,
|
||||||
|
valid: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { t, loading } = this.props;
|
||||||
|
const { name, type, groupPermission } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="subtitle">
|
||||||
|
{t("permission.add-permission.add-permission-heading")}
|
||||||
|
</h2>
|
||||||
|
<form onSubmit={this.submit}>
|
||||||
|
<InputField
|
||||||
|
label={t("permission.name")}
|
||||||
|
value={name ? name : ""}
|
||||||
|
onChange={this.handleNameChange}
|
||||||
|
validationError={!this.state.valid}
|
||||||
|
errorMessage={t("permission.add-permission.name-input-invalid")}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t("permission.group-permission")}
|
||||||
|
checked={groupPermission ? groupPermission : false}
|
||||||
|
onChange={this.handleGroupPermissionChange}
|
||||||
|
/>
|
||||||
|
<TypeSelector
|
||||||
|
label={t("permission.type")}
|
||||||
|
handleTypeChange={this.handleTypeChange}
|
||||||
|
type={type ? type : "READ"}
|
||||||
|
/>
|
||||||
|
<SubmitButton
|
||||||
|
label={t("permission.add-permission.submit-button")}
|
||||||
|
loading={loading}
|
||||||
|
disabled={!this.state.valid || this.state.name == ""}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = e => {
|
||||||
|
this.props.createPermission({
|
||||||
|
name: this.state.name,
|
||||||
|
type: this.state.type,
|
||||||
|
groupPermission: this.state.groupPermission
|
||||||
|
});
|
||||||
|
this.removeState();
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
removeState = () => {
|
||||||
|
this.setState({
|
||||||
|
name: "",
|
||||||
|
type: "READ",
|
||||||
|
groupPermission: false,
|
||||||
|
valid: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTypeChange = (type: string) => {
|
||||||
|
this.setState({
|
||||||
|
type: type
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNameChange = (name: string) => {
|
||||||
|
this.setState({
|
||||||
|
name: name,
|
||||||
|
valid: validator.isPermissionValid(
|
||||||
|
name,
|
||||||
|
this.state.groupPermission,
|
||||||
|
this.props.currentPermissions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleGroupPermissionChange = (groupPermission: boolean) => {
|
||||||
|
this.setState({
|
||||||
|
groupPermission: groupPermission,
|
||||||
|
valid: validator.isPermissionValid(
|
||||||
|
this.state.name,
|
||||||
|
groupPermission,
|
||||||
|
this.props.currentPermissions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("repos")(CreatePermissionForm);
|
||||||
40
scm-ui/src/repos/permissions/components/TypeSelector.js
Normal file
40
scm-ui/src/repos/permissions/components/TypeSelector.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// @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,
|
||||||
|
loading?: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class TypeSelector extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { type, handleTypeChange, loading } = this.props;
|
||||||
|
const types = ["READ", "OWNER", "WRITE"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
onChange={handleTypeChange}
|
||||||
|
value={type ? type : "READ"}
|
||||||
|
options={this.createSelectOptions(types)}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSelectOptions(types: string[]) {
|
||||||
|
return types.map(type => {
|
||||||
|
return {
|
||||||
|
label: type,
|
||||||
|
value: type
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("permissions")(TypeSelector);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { Permission } from "@scm-manager/ui-types";
|
||||||
|
import { confirmAlert, DeleteButton } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
confirmDialog?: boolean,
|
||||||
|
t: string => string,
|
||||||
|
deletePermission: (
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) => void,
|
||||||
|
loading: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeletePermissionButton extends React.Component<Props> {
|
||||||
|
static defaultProps = {
|
||||||
|
confirmDialog: true
|
||||||
|
};
|
||||||
|
|
||||||
|
deletePermission = () => {
|
||||||
|
this.props.deletePermission(
|
||||||
|
this.props.permission,
|
||||||
|
this.props.namespace,
|
||||||
|
this.props.repoName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
confirmDelete = () => {
|
||||||
|
const { t } = this.props;
|
||||||
|
confirmAlert({
|
||||||
|
title: t("permission.delete-permission-button.confirm-alert.title"),
|
||||||
|
message: t("permission.delete-permission-button.confirm-alert.message"),
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: t("permission.delete-permission-button.confirm-alert.submit"),
|
||||||
|
onClick: () => this.deletePermission()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("permission.delete-permission-button.confirm-alert.cancel"),
|
||||||
|
onClick: () => null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
isDeletable = () => {
|
||||||
|
return this.props.permission._links.delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { confirmDialog, loading, t } = this.props;
|
||||||
|
const action = confirmDialog ? this.confirmDelete : this.deletePermission;
|
||||||
|
|
||||||
|
if (!this.isDeletable()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DeleteButton
|
||||||
|
label={t("permission.delete-permission-button.label")}
|
||||||
|
action={action}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("repos")(DeletePermissionButton);
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { mount, shallow } from "enzyme";
|
||||||
|
import "../../../../tests/enzyme";
|
||||||
|
import "../../../../tests/i18n";
|
||||||
|
import DeletePermissionButton from "./DeletePermissionButton";
|
||||||
|
|
||||||
|
import { confirmAlert } from "@scm-manager/ui-components";
|
||||||
|
jest.mock("@scm-manager/ui-components", () => ({
|
||||||
|
confirmAlert: jest.fn(),
|
||||||
|
DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("DeletePermissionButton", () => {
|
||||||
|
it("should render nothing, if the delete link is missing", () => {
|
||||||
|
const permission = {
|
||||||
|
_links: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLink = shallow(
|
||||||
|
<DeletePermissionButton
|
||||||
|
permission={permission}
|
||||||
|
deletePermission={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(navLink.text()).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the navLink", () => {
|
||||||
|
const permission = {
|
||||||
|
_links: {
|
||||||
|
delete: {
|
||||||
|
href: "/permission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLink = mount(
|
||||||
|
<DeletePermissionButton
|
||||||
|
permission={permission}
|
||||||
|
deletePermission={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(navLink.text()).not.toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should open the confirm dialog on button click", () => {
|
||||||
|
const permission = {
|
||||||
|
_links: {
|
||||||
|
delete: {
|
||||||
|
href: "/permission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const button = mount(
|
||||||
|
<DeletePermissionButton
|
||||||
|
permission={permission}
|
||||||
|
deletePermission={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
button.find("button").simulate("click");
|
||||||
|
|
||||||
|
expect(confirmAlert.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the delete permission function with delete url", () => {
|
||||||
|
const permission = {
|
||||||
|
_links: {
|
||||||
|
delete: {
|
||||||
|
href: "/permission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let calledUrl = null;
|
||||||
|
function capture(permission) {
|
||||||
|
calledUrl = permission._links.delete.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = mount(
|
||||||
|
<DeletePermissionButton
|
||||||
|
permission={permission}
|
||||||
|
confirmDialog={false}
|
||||||
|
deletePermission={capture}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
button.find("button").simulate("click");
|
||||||
|
|
||||||
|
expect(calledUrl).toBe("/permission");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// @flow
|
||||||
|
import { validation } from "@scm-manager/ui-components";
|
||||||
|
import type {
|
||||||
|
PermissionCollection,
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
|
const isNameValid = validation.isNameValid;
|
||||||
|
|
||||||
|
export { isNameValid };
|
||||||
|
|
||||||
|
export const isPermissionValid = (name: string, groupPermission: boolean, permissions: PermissionCollection) => {
|
||||||
|
return isNameValid(name) && !currentPermissionIncludeName(name, groupPermission, permissions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentPermissionIncludeName = (name: string, groupPermission: boolean, permissions: PermissionCollection) => {
|
||||||
|
for (let i = 0; i < permissions.length; i++) {
|
||||||
|
if (
|
||||||
|
permissions[i].name === name &&
|
||||||
|
permissions[i].groupPermission == groupPermission
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//@flow
|
||||||
|
import * as validator from "./permissionValidation";
|
||||||
|
|
||||||
|
describe("permission validation", () => {
|
||||||
|
it("should return true if permission is valid and does not exist", () => {
|
||||||
|
const permissions = [];
|
||||||
|
const name = "PermissionName";
|
||||||
|
const groupPermission = false;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validator.isPermissionValid(name, groupPermission, permissions)
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true if permission is valid and does not exists with same group permission", () => {
|
||||||
|
const permissions = [
|
||||||
|
{
|
||||||
|
name: "PermissionName",
|
||||||
|
groupPermission: true,
|
||||||
|
type: "READ"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const name = "PermissionName";
|
||||||
|
const groupPermission = false;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validator.isPermissionValid(name, groupPermission, permissions)
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if permission is valid but exists", () => {
|
||||||
|
const permissions = [
|
||||||
|
{
|
||||||
|
name: "PermissionName",
|
||||||
|
groupPermission: false,
|
||||||
|
type: "READ"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const name = "PermissionName";
|
||||||
|
const groupPermission = false;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validator.isPermissionValid(name, groupPermission, permissions)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if permission does not exist but is invalid", () => {
|
||||||
|
const permissions = [];
|
||||||
|
const name = "@PermissionName";
|
||||||
|
const groupPermission = false;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validator.isPermissionValid(name, groupPermission, permissions)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if permission is not valid and does not exist", () => {
|
||||||
|
const permissions = [];
|
||||||
|
const name = "@PermissionName";
|
||||||
|
const groupPermission = false;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validator.isPermissionValid(name, groupPermission, permissions)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
200
scm-ui/src/repos/permissions/containers/Permissions.js
Normal file
200
scm-ui/src/repos/permissions/containers/Permissions.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
//@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,
|
||||||
|
PermissionEntry
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
|
import SinglePermission from "./SinglePermission";
|
||||||
|
import CreatePermissionForm from "../components/CreatePermissionForm";
|
||||||
|
import type { History } from "history";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
loading: boolean,
|
||||||
|
error: Error,
|
||||||
|
permissions: PermissionCollection,
|
||||||
|
hasPermissionToCreate: boolean,
|
||||||
|
loadingCreatePermission: boolean,
|
||||||
|
|
||||||
|
//dispatch functions
|
||||||
|
fetchPermissions: (namespace: string, repoName: string) => void,
|
||||||
|
createPermission: (
|
||||||
|
permission: PermissionEntry,
|
||||||
|
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
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
createPermissionReset(namespace, repoName);
|
||||||
|
modifyPermissionReset(namespace, repoName);
|
||||||
|
deletePermissionReset(namespace, repoName);
|
||||||
|
fetchPermissions(namespace, repoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPermission = (permission: Permission) => {
|
||||||
|
this.props.createPermission(
|
||||||
|
permission,
|
||||||
|
this.props.namespace,
|
||||||
|
this.props.repoName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
permissions,
|
||||||
|
t,
|
||||||
|
namespace,
|
||||||
|
repoName,
|
||||||
|
loadingCreatePermission,
|
||||||
|
hasPermissionToCreate
|
||||||
|
} = 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}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="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>
|
||||||
|
</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);
|
||||||
|
return {
|
||||||
|
namespace,
|
||||||
|
repoName,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
permissions,
|
||||||
|
hasPermissionToCreate,
|
||||||
|
loadingCreatePermission
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchPermissions: (namespace: string, repoName: string) => {
|
||||||
|
dispatch(fetchPermissions(namespace, repoName));
|
||||||
|
},
|
||||||
|
createPermission: (
|
||||||
|
permission: PermissionEntry,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
callback?: () => void
|
||||||
|
) => {
|
||||||
|
dispatch(createPermission(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));
|
||||||
176
scm-ui/src/repos/permissions/containers/SinglePermission.js
Normal file
176
scm-ui/src/repos/permissions/containers/SinglePermission.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
// @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));
|
||||||
624
scm-ui/src/repos/permissions/modules/permissions.js
Normal file
624
scm-ui/src/repos/permissions/modules/permissions.js
Normal file
@@ -0,0 +1,624 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
|
import * as types from "../../../modules/types";
|
||||||
|
import type { Action } from "@scm-manager/ui-components";
|
||||||
|
import type {
|
||||||
|
PermissionCollection,
|
||||||
|
Permission,
|
||||||
|
PermissionEntry
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
|
import { isPending } from "../../../modules/pending";
|
||||||
|
import { getFailure } from "../../../modules/failure";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
|
||||||
|
export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS";
|
||||||
|
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
|
||||||
|
types.PENDING_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const FETCH_PERMISSIONS_SUCCESS = `${FETCH_PERMISSIONS}_${
|
||||||
|
types.SUCCESS_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const FETCH_PERMISSIONS_FAILURE = `${FETCH_PERMISSIONS}_${
|
||||||
|
types.FAILURE_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const MODIFY_PERMISSION = "scm/permissions/MODFIY_PERMISSION";
|
||||||
|
export const MODIFY_PERMISSION_PENDING = `${MODIFY_PERMISSION}_${
|
||||||
|
types.PENDING_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const MODIFY_PERMISSION_SUCCESS = `${MODIFY_PERMISSION}_${
|
||||||
|
types.SUCCESS_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const MODIFY_PERMISSION_FAILURE = `${MODIFY_PERMISSION}_${
|
||||||
|
types.FAILURE_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const MODIFY_PERMISSION_RESET = `${MODIFY_PERMISSION}_${
|
||||||
|
types.RESET_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const CREATE_PERMISSION = "scm/permissions/CREATE_PERMISSION";
|
||||||
|
export const CREATE_PERMISSION_PENDING = `${CREATE_PERMISSION}_${
|
||||||
|
types.PENDING_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const CREATE_PERMISSION_SUCCESS = `${CREATE_PERMISSION}_${
|
||||||
|
types.SUCCESS_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const CREATE_PERMISSION_FAILURE = `${CREATE_PERMISSION}_${
|
||||||
|
types.FAILURE_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const CREATE_PERMISSION_RESET = `${CREATE_PERMISSION}_${
|
||||||
|
types.RESET_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const DELETE_PERMISSION = "scm/permissions/DELETE_PERMISSION";
|
||||||
|
export const DELETE_PERMISSION_PENDING = `${DELETE_PERMISSION}_${
|
||||||
|
types.PENDING_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const DELETE_PERMISSION_SUCCESS = `${DELETE_PERMISSION}_${
|
||||||
|
types.SUCCESS_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const DELETE_PERMISSION_FAILURE = `${DELETE_PERMISSION}_${
|
||||||
|
types.FAILURE_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const DELETE_PERMISSION_RESET = `${DELETE_PERMISSION}_${
|
||||||
|
types.RESET_SUFFIX
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const REPOS_URL = "repositories";
|
||||||
|
const PERMISSIONS_URL = "permissions";
|
||||||
|
const CONTENT_TYPE = "application/vnd.scmm-permission+json";
|
||||||
|
|
||||||
|
// fetch permissions
|
||||||
|
|
||||||
|
export function fetchPermissions(namespace: string, repoName: string) {
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(fetchPermissionsPending(namespace, repoName));
|
||||||
|
return apiClient
|
||||||
|
.get(`${REPOS_URL}/${namespace}/${repoName}/${PERMISSIONS_URL}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(permissions => {
|
||||||
|
dispatch(fetchPermissionsSuccess(permissions, namespace, repoName));
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dispatch(fetchPermissionsFailure(namespace, repoName, err));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPermissionsPending(
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_PERMISSIONS_PENDING,
|
||||||
|
payload: {
|
||||||
|
namespace,
|
||||||
|
repoName
|
||||||
|
},
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPermissionsSuccess(
|
||||||
|
permissions: any,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_PERMISSIONS_SUCCESS,
|
||||||
|
payload: permissions,
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPermissionsFailure(
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
error: Error
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_PERMISSIONS_FAILURE,
|
||||||
|
payload: {
|
||||||
|
namespace,
|
||||||
|
repoName,
|
||||||
|
error
|
||||||
|
},
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify permission
|
||||||
|
|
||||||
|
export function modifyPermission(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
callback?: () => void
|
||||||
|
) {
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(modifyPermissionPending(permission, namespace, repoName));
|
||||||
|
return apiClient
|
||||||
|
.put(permission._links.update.href, permission, CONTENT_TYPE)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(modifyPermissionSuccess(permission, namespace, repoName));
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(cause => {
|
||||||
|
const error = new Error(
|
||||||
|
`failed to modify permission: ${cause.message}`
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
modifyPermissionFailure(permission, error, namespace, repoName)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyPermissionPending(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: MODIFY_PERMISSION_PENDING,
|
||||||
|
payload: permission,
|
||||||
|
itemId: createItemId(permission, namespace, repoName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyPermissionSuccess(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: MODIFY_PERMISSION_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
permission,
|
||||||
|
position: namespace + "/" + repoName
|
||||||
|
},
|
||||||
|
itemId: createItemId(permission, namespace, repoName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyPermissionFailure(
|
||||||
|
permission: Permission,
|
||||||
|
error: Error,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: MODIFY_PERMISSION_FAILURE,
|
||||||
|
payload: { error, permission },
|
||||||
|
itemId: createItemId(permission, namespace, repoName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPermissions(
|
||||||
|
oldPermissions: PermissionCollection,
|
||||||
|
newPermission: Permission
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < oldPermissions.length; i++) {
|
||||||
|
if (oldPermissions[i].name === newPermission.name) {
|
||||||
|
oldPermissions.splice(i, 1, newPermission);
|
||||||
|
return oldPermissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyPermissionReset(namespace: string, repoName: string) {
|
||||||
|
return {
|
||||||
|
type: MODIFY_PERMISSION_RESET,
|
||||||
|
payload: {
|
||||||
|
namespace,
|
||||||
|
repoName
|
||||||
|
},
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// create permission
|
||||||
|
export function createPermission(
|
||||||
|
permission: PermissionEntry,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
callback?: () => void
|
||||||
|
) {
|
||||||
|
return function(dispatch: Dispatch) {
|
||||||
|
dispatch(createPermissionPending(permission, namespace, repoName));
|
||||||
|
return apiClient
|
||||||
|
.post(
|
||||||
|
`${REPOS_URL}/${namespace}/${repoName}/${PERMISSIONS_URL}`,
|
||||||
|
permission,
|
||||||
|
CONTENT_TYPE
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
const location = response.headers.get("Location");
|
||||||
|
return apiClient.get(location);
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(createdPermission => {
|
||||||
|
dispatch(
|
||||||
|
createPermissionSuccess(createdPermission, namespace, repoName)
|
||||||
|
);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err =>
|
||||||
|
dispatch(
|
||||||
|
createPermissionFailure(
|
||||||
|
new Error(
|
||||||
|
`failed to add permission ${permission.name}: ${err.message}`
|
||||||
|
),
|
||||||
|
namespace,
|
||||||
|
repoName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPermissionPending(
|
||||||
|
permission: PermissionEntry,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: CREATE_PERMISSION_PENDING,
|
||||||
|
payload: permission,
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPermissionSuccess(
|
||||||
|
permission: PermissionEntry,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: CREATE_PERMISSION_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
permission,
|
||||||
|
position: namespace + "/" + repoName
|
||||||
|
},
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPermissionFailure(
|
||||||
|
error: Error,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: CREATE_PERMISSION_FAILURE,
|
||||||
|
payload: error,
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPermissionReset(namespace: string, repoName: string) {
|
||||||
|
return {
|
||||||
|
type: CREATE_PERMISSION_RESET,
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete permission
|
||||||
|
|
||||||
|
export function deletePermission(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
callback?: () => void
|
||||||
|
) {
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(deletePermissionPending(permission, namespace, repoName));
|
||||||
|
return apiClient
|
||||||
|
.delete(permission._links.delete.href)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(deletePermissionSuccess(permission, namespace, repoName));
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(cause => {
|
||||||
|
const error = new Error(
|
||||||
|
`could not delete permission ${permission.name}: ${cause.message}`
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
deletePermissionFailure(permission, namespace, repoName, error)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePermissionPending(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: DELETE_PERMISSION_PENDING,
|
||||||
|
payload: permission,
|
||||||
|
itemId: createItemId(permission, namespace, repoName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePermissionSuccess(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: DELETE_PERMISSION_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
permission,
|
||||||
|
position: namespace + "/" + repoName
|
||||||
|
},
|
||||||
|
itemId: createItemId(permission, namespace, repoName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePermissionFailure(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
error: Error
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: DELETE_PERMISSION_FAILURE,
|
||||||
|
payload: {
|
||||||
|
error,
|
||||||
|
permission
|
||||||
|
},
|
||||||
|
itemId: createItemId(permission, namespace, repoName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePermissionReset(namespace: string, repoName: string) {
|
||||||
|
return {
|
||||||
|
type: DELETE_PERMISSION_RESET,
|
||||||
|
payload: {
|
||||||
|
namespace,
|
||||||
|
repoName
|
||||||
|
},
|
||||||
|
itemId: namespace + "/" + repoName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function deletePermissionFromState(
|
||||||
|
oldPermissions: PermissionCollection,
|
||||||
|
permission: Permission
|
||||||
|
) {
|
||||||
|
let newPermission = [];
|
||||||
|
for (let i = 0; i < oldPermissions.length; i++) {
|
||||||
|
if (oldPermissions[i] !== permission) {
|
||||||
|
newPermission.push(oldPermissions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createItemId(
|
||||||
|
permission: Permission,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
let groupPermission = permission.groupPermission ? "@" : "";
|
||||||
|
return namespace + "/" + repoName + "/" + groupPermission + permission.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reducer
|
||||||
|
export default function reducer(
|
||||||
|
state: Object = {},
|
||||||
|
action: Action = { type: "UNKNOWN" }
|
||||||
|
): Object {
|
||||||
|
if (!action.payload) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
switch (action.type) {
|
||||||
|
case FETCH_PERMISSIONS_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[action.itemId]: {
|
||||||
|
entries: action.payload._embedded.permissions,
|
||||||
|
createPermission: action.payload._links.create ? true : false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case MODIFY_PERMISSION_SUCCESS:
|
||||||
|
const positionOfPermission = action.payload.position;
|
||||||
|
const newPermission = newPermissions(
|
||||||
|
state[action.payload.position].entries,
|
||||||
|
action.payload.permission
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[positionOfPermission]: {
|
||||||
|
...state[positionOfPermission],
|
||||||
|
entries: newPermission
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case CREATE_PERMISSION_SUCCESS:
|
||||||
|
// return state;
|
||||||
|
const position = action.payload.position;
|
||||||
|
const permissions = state[action.payload.position].entries;
|
||||||
|
permissions.push(action.payload.permission);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[position]: {
|
||||||
|
...state[position],
|
||||||
|
entries: permissions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case DELETE_PERMISSION_SUCCESS:
|
||||||
|
const permissionPosition = action.payload.position;
|
||||||
|
const new_Permissions = deletePermissionFromState(
|
||||||
|
state[action.payload.position].entries,
|
||||||
|
action.payload.permission
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[permissionPosition]: {
|
||||||
|
...state[permissionPosition],
|
||||||
|
entries: new_Permissions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectors
|
||||||
|
|
||||||
|
export function getPermissionsOfRepo(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
if (state.permissions && state.permissions[namespace + "/" + repoName]) {
|
||||||
|
const permissions = state.permissions[namespace + "/" + repoName].entries;
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchPermissionsPending(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
return isPending(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFetchPermissionsFailure(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
return getFailure(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isModifyPermissionPending(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
permission: Permission
|
||||||
|
) {
|
||||||
|
return isPending(
|
||||||
|
state,
|
||||||
|
MODIFY_PERMISSION,
|
||||||
|
createItemId(permission, namespace, repoName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModifyPermissionFailure(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
permission: Permission
|
||||||
|
) {
|
||||||
|
return getFailure(
|
||||||
|
state,
|
||||||
|
MODIFY_PERMISSION,
|
||||||
|
createItemId(permission, namespace, repoName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasCreatePermission(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
if (state.permissions && state.permissions[namespace + "/" + repoName])
|
||||||
|
return state.permissions[namespace + "/" + repoName].createPermission;
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCreatePermissionPending(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
return isPending(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
||||||
|
}
|
||||||
|
export function getCreatePermissionFailure(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
return getFailure(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDeletePermissionPending(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
permission: Permission
|
||||||
|
) {
|
||||||
|
return isPending(
|
||||||
|
state,
|
||||||
|
DELETE_PERMISSION,
|
||||||
|
createItemId(permission, namespace, repoName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeletePermissionFailure(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
|
permission: Permission
|
||||||
|
) {
|
||||||
|
return getFailure(
|
||||||
|
state,
|
||||||
|
DELETE_PERMISSION,
|
||||||
|
createItemId(permission, namespace, repoName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeletePermissionsFailure(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
const permissions =
|
||||||
|
state.permissions && state.permissions[namespace + "/" + repoName]
|
||||||
|
? state.permissions[namespace + "/" + repoName].entries
|
||||||
|
: null;
|
||||||
|
if (permissions == null) return undefined;
|
||||||
|
for (let i = 0; i < permissions.length; i++) {
|
||||||
|
if (
|
||||||
|
getDeletePermissionFailure(state, namespace, repoName, permissions[i])
|
||||||
|
) {
|
||||||
|
return getFailure(
|
||||||
|
state,
|
||||||
|
DELETE_PERMISSION,
|
||||||
|
createItemId(permissions[i], namespace, repoName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModifyPermissionsFailure(
|
||||||
|
state: Object,
|
||||||
|
namespace: string,
|
||||||
|
repoName: string
|
||||||
|
) {
|
||||||
|
const permissions =
|
||||||
|
state.permissions && state.permissions[namespace + "/" + repoName]
|
||||||
|
? state.permissions[namespace + "/" + repoName].entries
|
||||||
|
: null;
|
||||||
|
if (permissions == null) return undefined;
|
||||||
|
for (let i = 0; i < permissions.length; i++) {
|
||||||
|
if (
|
||||||
|
getModifyPermissionFailure(state, namespace, repoName, permissions[i])
|
||||||
|
) {
|
||||||
|
return getFailure(
|
||||||
|
state,
|
||||||
|
MODIFY_PERMISSION,
|
||||||
|
createItemId(permissions[i], namespace, repoName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
769
scm-ui/src/repos/permissions/modules/permissions.test.js
Normal file
769
scm-ui/src/repos/permissions/modules/permissions.test.js
Normal file
@@ -0,0 +1,769 @@
|
|||||||
|
// @flow
|
||||||
|
import configureMockStore from "redux-mock-store";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import reducer, {
|
||||||
|
fetchPermissions,
|
||||||
|
fetchPermissionsSuccess,
|
||||||
|
getPermissionsOfRepo,
|
||||||
|
isFetchPermissionsPending,
|
||||||
|
getFetchPermissionsFailure,
|
||||||
|
modifyPermission,
|
||||||
|
modifyPermissionSuccess,
|
||||||
|
getModifyPermissionFailure,
|
||||||
|
isModifyPermissionPending,
|
||||||
|
createPermission,
|
||||||
|
hasCreatePermission,
|
||||||
|
deletePermission,
|
||||||
|
deletePermissionSuccess,
|
||||||
|
getDeletePermissionFailure,
|
||||||
|
isDeletePermissionPending,
|
||||||
|
getModifyPermissionsFailure,
|
||||||
|
MODIFY_PERMISSION_FAILURE,
|
||||||
|
MODIFY_PERMISSION_PENDING,
|
||||||
|
FETCH_PERMISSIONS,
|
||||||
|
FETCH_PERMISSIONS_PENDING,
|
||||||
|
FETCH_PERMISSIONS_SUCCESS,
|
||||||
|
FETCH_PERMISSIONS_FAILURE,
|
||||||
|
MODIFY_PERMISSION_SUCCESS,
|
||||||
|
MODIFY_PERMISSION,
|
||||||
|
CREATE_PERMISSION_PENDING,
|
||||||
|
CREATE_PERMISSION_SUCCESS,
|
||||||
|
CREATE_PERMISSION_FAILURE,
|
||||||
|
DELETE_PERMISSION,
|
||||||
|
DELETE_PERMISSION_PENDING,
|
||||||
|
DELETE_PERMISSION_SUCCESS,
|
||||||
|
DELETE_PERMISSION_FAILURE,
|
||||||
|
CREATE_PERMISSION,
|
||||||
|
createPermissionSuccess,
|
||||||
|
getCreatePermissionFailure,
|
||||||
|
isCreatePermissionPending,
|
||||||
|
getDeletePermissionsFailure
|
||||||
|
} from "./permissions";
|
||||||
|
import type { Permission, PermissionCollection } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
const hitchhiker_puzzle42Permission_user_eins: Permission = {
|
||||||
|
name: "user_eins",
|
||||||
|
type: "READ",
|
||||||
|
groupPermission: false,
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
||||||
|
name: "user_zwei",
|
||||||
|
type: "WRITE",
|
||||||
|
groupPermission: true,
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hitchhiker_puzzle42Permissions: PermissionCollection = [
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
hitchhiker_puzzle42Permission_user_zwei
|
||||||
|
];
|
||||||
|
|
||||||
|
const hitchhiker_puzzle42RepoPermissions = {
|
||||||
|
_embedded: {
|
||||||
|
permissions: hitchhiker_puzzle42Permissions
|
||||||
|
},
|
||||||
|
_links: {
|
||||||
|
create: {
|
||||||
|
href:
|
||||||
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("permission fetch", () => {
|
||||||
|
const REPOS_URL = "/api/v2/repositories";
|
||||||
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.reset();
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully fetch permissions to repo hitchhiker/puzzle42", () => {
|
||||||
|
fetchMock.getOnce(
|
||||||
|
REPOS_URL + "/hitchhiker/puzzle42/permissions",
|
||||||
|
hitchhiker_puzzle42RepoPermissions
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_PERMISSIONS_PENDING,
|
||||||
|
payload: {
|
||||||
|
namespace: "hitchhiker",
|
||||||
|
repoName: "puzzle42"
|
||||||
|
},
|
||||||
|
itemId: "hitchhiker/puzzle42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_PERMISSIONS_SUCCESS,
|
||||||
|
payload: hitchhiker_puzzle42RepoPermissions,
|
||||||
|
itemId: "hitchhiker/puzzle42"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(fetchPermissions("hitchhiker", "puzzle42"))
|
||||||
|
.then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch FETCH_PERMISSIONS_FAILURE, it the request fails", () => {
|
||||||
|
fetchMock.getOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(fetchPermissions("hitchhiker", "puzzle42"))
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(FETCH_PERMISSIONS_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(FETCH_PERMISSIONS_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully modify user_eins permission", () => {
|
||||||
|
fetchMock.putOnce(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||||
|
{
|
||||||
|
status: 204
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||||
|
editedPermission.type = "OWNER";
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
return store
|
||||||
|
.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42"))
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully modify user_eins permission and call the callback", () => {
|
||||||
|
fetchMock.putOnce(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||||
|
{
|
||||||
|
status: 204
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||||
|
editedPermission.type = "OWNER";
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
let called = false;
|
||||||
|
const callback = () => {
|
||||||
|
called = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
modifyPermission(editedPermission, "hitchhiker", "puzzle42", callback)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
|
||||||
|
expect(called).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail modifying on HTTP 500", () => {
|
||||||
|
fetchMock.putOnce(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||||
|
{
|
||||||
|
status: 500
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||||
|
editedPermission.type = "OWNER";
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
return store
|
||||||
|
.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42"))
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(MODIFY_PERMISSION_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add a permission successfully", () => {
|
||||||
|
// unmatched
|
||||||
|
fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||||
|
status: 204,
|
||||||
|
headers: {
|
||||||
|
location: "repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchMock.getOnce(
|
||||||
|
REPOS_URL + "/hitchhiker/puzzle42/permissions/user_eins",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
);
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
createPermission(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(CREATE_PERMISSION_SUCCESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail adding a permission on HTTP 500", () => {
|
||||||
|
fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
createPermission(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(CREATE_PERMISSION_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the callback after permission successfully created", () => {
|
||||||
|
// unmatched
|
||||||
|
fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||||
|
status: 204,
|
||||||
|
headers: {
|
||||||
|
location: "repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchMock.getOnce(
|
||||||
|
REPOS_URL + "/hitchhiker/puzzle42/permissions/user_eins",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
);
|
||||||
|
let callMe = "not yet";
|
||||||
|
|
||||||
|
const callback = () => {
|
||||||
|
callMe = "yeah";
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
createPermission(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
expect(callMe).toBe("yeah");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should delete successfully permission user_eins", () => {
|
||||||
|
fetchMock.deleteOnce(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||||
|
{
|
||||||
|
status: 204
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
deletePermission(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions.length).toBe(2);
|
||||||
|
expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
|
||||||
|
expect(actions[0].payload).toBe(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
);
|
||||||
|
expect(actions[1].type).toEqual(DELETE_PERMISSION_SUCCESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the callback, after successful delete", () => {
|
||||||
|
fetchMock.deleteOnce(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||||
|
{
|
||||||
|
status: 204
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let called = false;
|
||||||
|
const callMe = () => {
|
||||||
|
called = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
deletePermission(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
callMe
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
expect(called).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to delete permission", () => {
|
||||||
|
fetchMock.deleteOnce(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||||
|
{
|
||||||
|
status: 500
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(
|
||||||
|
deletePermission(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
|
||||||
|
expect(actions[0].payload).toBe(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
);
|
||||||
|
expect(actions[1].type).toEqual(DELETE_PERMISSION_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("permissions reducer", () => {
|
||||||
|
it("should return empty object, if state and action is undefined", () => {
|
||||||
|
expect(reducer()).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the same state, if the action is undefined", () => {
|
||||||
|
const state = { x: true };
|
||||||
|
expect(reducer(state)).toBe(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the same state, if the action is unknown to the reducer", () => {
|
||||||
|
const state = { x: true };
|
||||||
|
expect(reducer(state, { type: "EL_SPECIALE" })).toBe(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should store the permissions on FETCH_PERMISSION_SUCCESS", () => {
|
||||||
|
const newState = reducer(
|
||||||
|
{},
|
||||||
|
fetchPermissionsSuccess(
|
||||||
|
hitchhiker_puzzle42RepoPermissions,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(newState["hitchhiker/puzzle42"].entries).toBe(
|
||||||
|
hitchhiker_puzzle42Permissions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update permission", () => {
|
||||||
|
const oldState = {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: [hitchhiker_puzzle42Permission_user_eins]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins };
|
||||||
|
permissionEdited.type = "OWNER";
|
||||||
|
let expectedState = {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: [permissionEdited]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const newState = reducer(
|
||||||
|
oldState,
|
||||||
|
modifyPermissionSuccess(permissionEdited, "hitchhiker", "puzzle42")
|
||||||
|
);
|
||||||
|
expect(newState["hitchhiker/puzzle42"]).toEqual(
|
||||||
|
expectedState["hitchhiker/puzzle42"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove permission from state when delete succeeds", () => {
|
||||||
|
const state = {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: [
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
hitchhiker_puzzle42Permission_user_zwei
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedState = {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: [hitchhiker_puzzle42Permission_user_zwei]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const newState = reducer(
|
||||||
|
state,
|
||||||
|
deletePermissionSuccess(
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(newState["hitchhiker/puzzle42"]).toEqual(
|
||||||
|
expectedState["hitchhiker/puzzle42"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add permission", () => {
|
||||||
|
//changing state had to be removed because of errors
|
||||||
|
const oldState = {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: [hitchhiker_puzzle42Permission_user_eins]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let expectedState = {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: [
|
||||||
|
hitchhiker_puzzle42Permission_user_eins,
|
||||||
|
hitchhiker_puzzle42Permission_user_zwei
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const newState = reducer(
|
||||||
|
oldState,
|
||||||
|
createPermissionSuccess(
|
||||||
|
hitchhiker_puzzle42Permission_user_zwei,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(newState["hitchhiker/puzzle42"]).toEqual(
|
||||||
|
expectedState["hitchhiker/puzzle42"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("permissions selectors", () => {
|
||||||
|
const error = new Error("something goes wrong");
|
||||||
|
|
||||||
|
it("should return the permissions of one repository", () => {
|
||||||
|
const state = {
|
||||||
|
permissions: {
|
||||||
|
"hitchhiker/puzzle42": {
|
||||||
|
entries: hitchhiker_puzzle42Permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const repoPermissions = getPermissionsOfRepo(
|
||||||
|
state,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42"
|
||||||
|
);
|
||||||
|
expect(repoPermissions).toEqual(hitchhiker_puzzle42Permissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when fetch permissions is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_PERMISSIONS + "/hitchhiker/puzzle42"]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(isFetchPermissionsPending(state, "hitchhiker", "puzzle42")).toEqual(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when fetch permissions is not pending", () => {
|
||||||
|
expect(isFetchPermissionsPending({}, "hitchiker", "puzzle42")).toEqual(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when fetch permissions did fail", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_PERMISSIONS + "/hitchhiker/puzzle42"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getFetchPermissionsFailure(state, "hitchhiker", "puzzle42")).toEqual(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when fetch permissions did not fail", () => {
|
||||||
|
expect(getFetchPermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when modify permission is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
isModifyPermissionPending(
|
||||||
|
state,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when modify permission is not pending", () => {
|
||||||
|
expect(
|
||||||
|
isModifyPermissionPending(
|
||||||
|
{},
|
||||||
|
"hitchiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when modify permission did fail", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getModifyPermissionFailure(
|
||||||
|
state,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when modify permission did not fail", () => {
|
||||||
|
expect(
|
||||||
|
getModifyPermissionFailure(
|
||||||
|
{},
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when one of the modify permissions did fail", () => {
|
||||||
|
const state = {
|
||||||
|
permissions: {
|
||||||
|
"hitchhiker/puzzle42": { entries: hitchhiker_puzzle42Permissions }
|
||||||
|
},
|
||||||
|
failure: {
|
||||||
|
[MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getModifyPermissionsFailure(state, "hitchhiker", "puzzle42")
|
||||||
|
).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when no modify permissions did not fail", () => {
|
||||||
|
expect(getModifyPermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when createPermission is true", () => {
|
||||||
|
const state = {
|
||||||
|
permissions: {
|
||||||
|
["hitchhiker/puzzle42"]: {
|
||||||
|
createPermission: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(hasCreatePermission(state, "hitchhiker", "puzzle42")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when createPermission is false", () => {
|
||||||
|
const state = {
|
||||||
|
permissions: {
|
||||||
|
["hitchhiker/puzzle42"]: {
|
||||||
|
createPermission: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(hasCreatePermission(state, "hitchhiker", "puzzle42")).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when delete permission is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
isDeletePermissionPending(
|
||||||
|
state,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when delete permission is not pending", () => {
|
||||||
|
expect(
|
||||||
|
isDeletePermissionPending(
|
||||||
|
{},
|
||||||
|
"hitchiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when delete permission did fail", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getDeletePermissionFailure(
|
||||||
|
state,
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when delete permission did not fail", () => {
|
||||||
|
expect(
|
||||||
|
getDeletePermissionFailure(
|
||||||
|
{},
|
||||||
|
"hitchhiker",
|
||||||
|
"puzzle42",
|
||||||
|
hitchhiker_puzzle42Permission_user_eins
|
||||||
|
)
|
||||||
|
).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when one of the delete permissions did fail", () => {
|
||||||
|
const state = {
|
||||||
|
permissions: {
|
||||||
|
"hitchhiker/puzzle42": { entries: hitchhiker_puzzle42Permissions }
|
||||||
|
},
|
||||||
|
failure: {
|
||||||
|
[DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getDeletePermissionsFailure(state, "hitchhiker", "puzzle42")
|
||||||
|
).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when no delete permissions did not fail", () => {
|
||||||
|
expect(getDeletePermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when create permission is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[CREATE_PERMISSION + "/hitchhiker/puzzle42"]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(isCreatePermissionPending(state, "hitchhiker", "puzzle42")).toEqual(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when create permissions is not pending", () => {
|
||||||
|
expect(isCreatePermissionPending({}, "hitchiker", "puzzle42")).toEqual(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when create permissions did fail", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[CREATE_PERMISSION + "/hitchhiker/puzzle42"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getCreatePermissionFailure(state, "hitchhiker", "puzzle42")).toEqual(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when create permissions did not fail", () => {
|
||||||
|
expect(getCreatePermissionFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -97,6 +97,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
value={user ? user.name : ""}
|
value={user ? user.name : ""}
|
||||||
validationError={this.state.nameValidationError}
|
validationError={this.state.nameValidationError}
|
||||||
errorMessage={t("validation.name-invalid")}
|
errorMessage={t("validation.name-invalid")}
|
||||||
|
helpText={t("help.usernameHelpText")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -109,6 +110,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
value={user ? user.displayName : ""}
|
value={user ? user.displayName : ""}
|
||||||
validationError={this.state.displayNameValidationError}
|
validationError={this.state.displayNameValidationError}
|
||||||
errorMessage={t("validation.displayname-invalid")}
|
errorMessage={t("validation.displayname-invalid")}
|
||||||
|
helpText={t("help.displayNameHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("user.mail")}
|
label={t("user.mail")}
|
||||||
@@ -116,6 +118,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
value={user ? user.mail : ""}
|
value={user ? user.mail : ""}
|
||||||
validationError={this.state.mailValidationError}
|
validationError={this.state.mailValidationError}
|
||||||
errorMessage={t("validation.mail-invalid")}
|
errorMessage={t("validation.mail-invalid")}
|
||||||
|
helpText={t("help.mailHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("user.password")}
|
label={t("user.password")}
|
||||||
@@ -124,6 +127,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
value={user ? user.password : ""}
|
value={user ? user.password : ""}
|
||||||
validationError={this.state.validatePasswordError}
|
validationError={this.state.validatePasswordError}
|
||||||
errorMessage={t("validation.password-invalid")}
|
errorMessage={t("validation.password-invalid")}
|
||||||
|
helpText={t("help.passwordHelpText")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label={t("validation.validatePassword")}
|
label={t("validation.validatePassword")}
|
||||||
@@ -132,16 +136,19 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
value={this.state ? this.state.validatePassword : ""}
|
value={this.state ? this.state.validatePassword : ""}
|
||||||
validationError={this.state.passwordValidationError}
|
validationError={this.state.passwordValidationError}
|
||||||
errorMessage={t("validation.passwordValidation-invalid")}
|
errorMessage={t("validation.passwordValidation-invalid")}
|
||||||
|
helpText={t("help.passwordConfirmHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("user.admin")}
|
label={t("user.admin")}
|
||||||
onChange={this.handleAdminChange}
|
onChange={this.handleAdminChange}
|
||||||
checked={user ? user.admin : false}
|
checked={user ? user.admin : false}
|
||||||
|
helpText={t("help.adminHelpText")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("user.active")}
|
label={t("user.active")}
|
||||||
onChange={this.handleActiveChange}
|
onChange={this.handleActiveChange}
|
||||||
checked={user ? user.active : false}
|
checked={user ? user.active : false}
|
||||||
|
helpText={t("help.activeHelpText")}
|
||||||
/>
|
/>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
disabled={!this.isValid()}
|
disabled={!this.isValid()}
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ export const isDisplayNameValid = (displayName: string) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
export const isPasswordValid = (password: string) => {
|
export const isPasswordValid = (password: string) => {
|
||||||
return password.length > 6 && password.length < 32;
|
return password.length >= 6 && password.length < 32;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,13 +61,13 @@ const userZaphod = {
|
|||||||
properties: {},
|
properties: {},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/zaphod"
|
href: "http://localhost:8081/api/v2/users/zaphod"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/zaphod"
|
href: "http://localhost:8081/api/v2/users/zaphod"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/zaphod"
|
href: "http://localhost:8081/api/v2/users/zaphod"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -84,13 +84,13 @@ const userFord = {
|
|||||||
properties: {},
|
properties: {},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/ford"
|
href: "http://localhost:8081/api/v2/users/ford"
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/ford"
|
href: "http://localhost:8081/api/v2/users/ford"
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
href: "http://localhost:8081/api/rest/v2/users/ford"
|
href: "http://localhost:8081/api/v2/users/ford"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -100,16 +100,16 @@ const responseBody = {
|
|||||||
pageTotal: 1,
|
pageTotal: 1,
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: "http://localhost:3000/api/rest/v2/users/?page=0&pageSize=10"
|
href: "http://localhost:3000/api/v2/users/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
first: {
|
first: {
|
||||||
href: "http://localhost:3000/api/rest/v2/users/?page=0&pageSize=10"
|
href: "http://localhost:3000/api/v2/users/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
last: {
|
last: {
|
||||||
href: "http://localhost:3000/api/rest/v2/users/?page=0&pageSize=10"
|
href: "http://localhost:3000/api/v2/users/?page=0&pageSize=10"
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
href: "http://localhost:3000/api/rest/v2/users/"
|
href: "http://localhost:3000/api/v2/users/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_embedded: {
|
_embedded: {
|
||||||
@@ -122,7 +122,7 @@ const response = {
|
|||||||
responseBody
|
responseBody
|
||||||
};
|
};
|
||||||
|
|
||||||
const USERS_URL = "/api/rest/v2/users";
|
const USERS_URL = "/api/v2/users";
|
||||||
|
|
||||||
const error = new Error("KAPUTT");
|
const error = new Error("KAPUTT");
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ describe("users fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("successfully update user", () => {
|
it("successfully update user", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/users/zaphod", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ describe("users fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call callback, after successful modified user", () => {
|
it("should call callback, after successful modified user", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/users/zaphod", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ describe("users fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail updating user on HTTP 500", () => {
|
it("should fail updating user on HTTP 500", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
|
fetchMock.putOnce("http://localhost:8081/api/v2/users/zaphod", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ describe("users fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should delete successfully user zaphod", () => {
|
it("should delete successfully user zaphod", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
|
fetchMock.deleteOnce("http://localhost:8081/api/v2/users/zaphod", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ describe("users fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call the callback, after successful delete", () => {
|
it("should call the callback, after successful delete", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
|
fetchMock.deleteOnce("http://localhost:8081/api/v2/users/zaphod", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ describe("users fetch()", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to delete user zaphod", () => {
|
it("should fail to delete user zaphod", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/api/rest/v2/users/zaphod", {
|
fetchMock.deleteOnce("http://localhost:8081/api/v2/users/zaphod", {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ $blue: #33B2E8;
|
|||||||
|
|
||||||
// 6. Import the rest of Bulma
|
// 6. Import the rest of Bulma
|
||||||
@import "bulma/bulma";
|
@import "bulma/bulma";
|
||||||
|
@import "bulma-tooltip/dist/css/bulma-tooltip";
|
||||||
|
|
||||||
// import at the end, because we need a lot of stuff from bulma/bulma
|
// import at the end, because we need a lot of stuff from bulma/bulma
|
||||||
.box-link-shadow {
|
.box-link-shadow {
|
||||||
|
|||||||
8736
scm-ui/yarn.lock
8736
scm-ui/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
|
|
||||||
<jaxrs datatype-detection="local">
|
<jaxrs datatype-detection="local">
|
||||||
<application path="/api/rest" />
|
<application path="/api" />
|
||||||
</jaxrs>
|
</jaxrs>
|
||||||
|
|
||||||
<docs disableResourceLinks="true" includeApplicationPath="true" />
|
<docs disableResourceLinks="true" includeApplicationPath="true" />
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
public static final String PATTERN_PLUGIN_SCRIPT = "/plugins/resources/js/*";
|
public static final String PATTERN_PLUGIN_SCRIPT = "/plugins/resources/js/*";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String PATTERN_RESTAPI = "/api/rest/*";
|
public static final String PATTERN_RESTAPI = "/api/*";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String PATTERN_SCRIPT = "*.js";
|
public static final String PATTERN_SCRIPT = "*.js";
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ public class GroupDto extends HalRepresentation {
|
|||||||
private Instant lastModified;
|
private Instant lastModified;
|
||||||
@Pattern(regexp = USER_GROUP_PATTERN)
|
@Pattern(regexp = USER_GROUP_PATTERN)
|
||||||
private String name;
|
private String name;
|
||||||
@NotEmpty
|
|
||||||
private String type;
|
private String type;
|
||||||
private Map<String, String> properties;
|
private Map<String, String> properties;
|
||||||
private List<String> members;
|
private List<String> members;
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ public class PermissionRootResource {
|
|||||||
checkPermissionAlreadyExists(permission, repository);
|
checkPermissionAlreadyExists(permission, repository);
|
||||||
repository.getPermissions().add(dtoToModelMapper.map(permission));
|
repository.getPermissions().add(dtoToModelMapper.map(permission));
|
||||||
manager.modify(repository);
|
manager.modify(repository);
|
||||||
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, permission.getName()))).build();
|
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
|
||||||
|
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ public abstract class PermissionToPermissionDtoMapper {
|
|||||||
*/
|
*/
|
||||||
@AfterMapping
|
@AfterMapping
|
||||||
void appendLinks(@MappingTarget PermissionDto target, @Context Repository repository) {
|
void appendLinks(@MappingTarget PermissionDto target, @Context Repository repository) {
|
||||||
String permissionName = Optional.of(target.getName())
|
String permissionName = getUrlPermissionName(target);
|
||||||
.filter(p -> !target.isGroupPermission())
|
|
||||||
.orElse(GROUP_PREFIX + target.getName());
|
|
||||||
Links.Builder linksBuilder = linkingTo()
|
Links.Builder linksBuilder = linkingTo()
|
||||||
.self(resourceLinks.permission().self(repository.getNamespace(), repository.getName(), permissionName));
|
.self(resourceLinks.permission().self(repository.getNamespace(), repository.getName(), permissionName));
|
||||||
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
|
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
|
||||||
@@ -52,4 +50,10 @@ public abstract class PermissionToPermissionDtoMapper {
|
|||||||
}
|
}
|
||||||
target.add(linksBuilder.build());
|
target.add(linksBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUrlPermissionName(PermissionDto permissionDto) {
|
||||||
|
return Optional.of(permissionDto.getName())
|
||||||
|
.filter(p -> !permissionDto.isGroupPermission())
|
||||||
|
.orElse(GROUP_PREFIX + permissionDto.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
<context-param>
|
<context-param>
|
||||||
<param-name>resteasy.servlet.mapping.prefix</param-name>
|
<param-name>resteasy.servlet.mapping.prefix</param-name>
|
||||||
<param-value>/api/rest</param-value>
|
<param-value>/api</param-value>
|
||||||
</context-param>
|
</context-param>
|
||||||
|
|
||||||
<servlet>
|
<servlet>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
<servlet-name>Resteasy</servlet-name>
|
<servlet-name>Resteasy</servlet-name>
|
||||||
<url-pattern>/api/rest/*</url-pattern>
|
<url-pattern>/api/*</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
<!-- capture sessions -->
|
<!-- capture sessions -->
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class SecurityFilterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDoOnAuthenticationUrlV1() throws IOException, ServletException {
|
public void testDoOnAuthenticationUrlV1() throws IOException, ServletException {
|
||||||
checkIfAuthenticationUrlIsPassedThrough("/scm/api/rest/auth/access_token");
|
checkIfAuthenticationUrlIsPassedThrough("/scm/api/auth/access_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,7 +118,7 @@ public class SecurityFilterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDoOnAuthenticationUrlV2() throws IOException, ServletException {
|
public void testDoOnAuthenticationUrlV2() throws IOException, ServletException {
|
||||||
checkIfAuthenticationUrlIsPassedThrough("/scm/api/rest/v2/auth/access_token");
|
checkIfAuthenticationUrlIsPassedThrough("/scm/api/v2/auth/access_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkIfAuthenticationUrlIsPassedThrough(String uri) throws IOException, ServletException {
|
private void checkIfAuthenticationUrlIsPassedThrough(String uri) throws IOException, ServletException {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public final class IntegrationTestUtil
|
|||||||
public static final String BASE_URL = "http://localhost:8081/scm/";
|
public static final String BASE_URL = "http://localhost:8081/scm/";
|
||||||
|
|
||||||
/** scm-manager base url for the rest api */
|
/** scm-manager base url for the rest api */
|
||||||
public static final String REST_BASE_URL = BASE_URL.concat("api/rest/v2/");
|
public static final String REST_BASE_URL = BASE_URL.concat("api/v2/");
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class SecurityRequestsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsAuthenticationRequestWithContextPath() {
|
public void testIsAuthenticationRequestWithContextPath() {
|
||||||
when(request.getRequestURI()).thenReturn("/scm/api/rest/auth/access_token");
|
when(request.getRequestURI()).thenReturn("/scm/api/auth/access_token");
|
||||||
when(request.getContextPath()).thenReturn("/scm");
|
when(request.getContextPath()).thenReturn("/scm");
|
||||||
|
|
||||||
assertTrue(SecurityRequests.isAuthenticationRequest(request));
|
assertTrue(SecurityRequests.isAuthenticationRequest(request));
|
||||||
@@ -29,9 +29,9 @@ public class SecurityRequestsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsAuthenticationRequest() throws Exception {
|
public void testIsAuthenticationRequest() throws Exception {
|
||||||
assertTrue(SecurityRequests.isAuthenticationRequest("/api/rest/auth/access_token"));
|
assertTrue(SecurityRequests.isAuthenticationRequest("/api/auth/access_token"));
|
||||||
assertTrue(SecurityRequests.isAuthenticationRequest("/api/rest/v2/auth/access_token"));
|
assertTrue(SecurityRequests.isAuthenticationRequest("/api/v2/auth/access_token"));
|
||||||
assertFalse(SecurityRequests.isAuthenticationRequest("/api/rest/repositories"));
|
assertFalse(SecurityRequests.isAuthenticationRequest("/api/repositories"));
|
||||||
assertFalse(SecurityRequests.isAuthenticationRequest("/api/rest/v2/repositories"));
|
assertFalse(SecurityRequests.isAuthenticationRequest("/api/v2/repositories"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user