mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
More UI changes
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 48 KiB |
@@ -1,56 +1,56 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import { MailLink } from "@scm-manager/ui-components";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
|
||||
// Context props
|
||||
t: string => string
|
||||
};
|
||||
type State = {};
|
||||
|
||||
class ProfileInfo extends React.Component<Props, State> {
|
||||
render() {
|
||||
const { me, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
<AvatarWrapper>
|
||||
<div>
|
||||
<figure className="media-left">
|
||||
<p className="image is-64x64">
|
||||
{
|
||||
// TODO: add avatar
|
||||
}
|
||||
</p>
|
||||
</figure>
|
||||
</div>
|
||||
</AvatarWrapper>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("profile.username")}</td>
|
||||
<td>{me.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("profile.displayName")}</td>
|
||||
<td>{me.displayName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("profile.mail")}</td>
|
||||
<td>
|
||||
<MailLink address={me.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(translate("commons"))(ProfileInfo);
|
||||
// @flow
|
||||
import React from "react";
|
||||
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import { MailLink } from "@scm-manager/ui-components";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
|
||||
// Context props
|
||||
t: string => string
|
||||
};
|
||||
type State = {};
|
||||
|
||||
class ProfileInfo extends React.Component<Props, State> {
|
||||
render() {
|
||||
const { me, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
<AvatarWrapper>
|
||||
<div>
|
||||
<figure className="media-left">
|
||||
<p className="image is-64x64">
|
||||
{
|
||||
// TODO: add avatar
|
||||
}
|
||||
</p>
|
||||
</figure>
|
||||
</div>
|
||||
</AvatarWrapper>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.username")}</td>
|
||||
<td>{me.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.displayName")}</td>
|
||||
<td>{me.displayName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.mail")}</td>
|
||||
<td>
|
||||
<MailLink address={me.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(translate("commons"))(ProfileInfo);
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import GroupMember from "./GroupMember";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class Details extends React.Component<Props> {
|
||||
render() {
|
||||
const { group, t } = this.props;
|
||||
return (
|
||||
<table className="table content">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("group.name")}</td>
|
||||
<td>{group.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("group.description")}</td>
|
||||
<td>{group.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("group.type")}</td>
|
||||
<td>{group.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("group.creationDate")}</td>
|
||||
<td>
|
||||
<DateFromNow date={group.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("group.lastModified")}</td>
|
||||
<td>
|
||||
<DateFromNow date={group.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
{this.renderMembers()}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderMembers() {
|
||||
if (this.props.group.members.length > 0) {
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
{this.props.t("group.members")}
|
||||
<ul>
|
||||
{this.props.group._embedded.members.map((member, index) => {
|
||||
return <GroupMember key={index} member={member} />;
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(Details);
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import GroupMember from "./GroupMember";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class Details extends React.Component<Props> {
|
||||
render() {
|
||||
const { group, t } = this.props;
|
||||
return (
|
||||
<table className="table content">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("group.name")}</td>
|
||||
<td>{group.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("group.description")}</td>
|
||||
<td>{group.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("group.type")}</td>
|
||||
<td>{group.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("group.creationDate")}</td>
|
||||
<td>
|
||||
<DateFromNow date={group.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("group.lastModified")}</td>
|
||||
<td>
|
||||
<DateFromNow date={group.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
{this.renderMembers()}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderMembers() {
|
||||
if (this.props.group.members.length > 0) {
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
{this.props.t("group.members")}
|
||||
<ul>
|
||||
{this.props.group._embedded.members.map((member, index) => {
|
||||
return <GroupMember key={index} member={member} />;
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(Details);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
group: Group
|
||||
};
|
||||
|
||||
export default class GroupRow extends React.Component<Props> {
|
||||
renderLink(to: string, label: string) {
|
||||
return <Link to={to}>{label}</Link>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const to = `/group/${group.name}`;
|
||||
return (
|
||||
<tr>
|
||||
<td>{this.renderLink(to, group.name)}</td>
|
||||
<td className="is-hidden-mobile">{group.description}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
group: Group
|
||||
};
|
||||
|
||||
export default class GroupRow extends React.Component<Props> {
|
||||
renderLink(to: string, label: string) {
|
||||
return <Link to={to}>{label}</Link>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const to = `/group/${group.name}`;
|
||||
return (
|
||||
<tr>
|
||||
<td>{this.renderLink(to, group.name)}</td>
|
||||
<td className="is-hidden-mobile">{group.description}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class RepositoryDetailTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, t } = this.props;
|
||||
return (
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("repository.name")}</td>
|
||||
<td>{repository.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("repository.type")}</td>
|
||||
<td>{repository.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("repository.contact")}</td>
|
||||
<td>
|
||||
<MailLink address={repository.contact} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("repository.description")}</td>
|
||||
<td>{repository.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("repository.creationDate")}</td>
|
||||
<td>
|
||||
<DateFromNow date={repository.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("repository.lastModified")}</td>
|
||||
<td>
|
||||
<DateFromNow date={repository.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(RepositoryDetailTable);
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class RepositoryDetailTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, t } = this.props;
|
||||
return (
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("repository.name")}</td>
|
||||
<td>{repository.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("repository.type")}</td>
|
||||
<td>{repository.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("repository.contact")}</td>
|
||||
<td>
|
||||
<MailLink address={repository.contact} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("repository.description")}</td>
|
||||
<td>{repository.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("repository.creationDate")}</td>
|
||||
<td>
|
||||
<DateFromNow date={repository.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("repository.lastModified")}</td>
|
||||
<td>
|
||||
<DateFromNow date={repository.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(RepositoryDetailTable);
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import RepositoryDetailTable from "./RepositoryDetailTable";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
};
|
||||
|
||||
class RepositoryDetails extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<RepositoryDetailTable repository={repository} />
|
||||
<div className="content">
|
||||
<ExtensionPoint
|
||||
name="repos.repository-details.information"
|
||||
renderAll={true}
|
||||
props={{ repository }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryDetails;
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import RepositoryDetailTable from "./RepositoryDetailTable";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
};
|
||||
|
||||
class RepositoryDetails extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<RepositoryDetailTable repository={repository} />
|
||||
<hr />
|
||||
<div className="content">
|
||||
<ExtensionPoint
|
||||
name="repos.repository-details.information"
|
||||
renderAll={true}
|
||||
props={{ repository }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryDetails;
|
||||
|
||||
@@ -1,113 +1,108 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import injectSheet from "react-jss";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import RepositoryEntryLink from "./RepositoryEntryLink";
|
||||
import classNames from "classnames";
|
||||
import RepositoryAvatar from "./RepositoryAvatar";
|
||||
|
||||
const styles = {
|
||||
outer: {
|
||||
position: "relative"
|
||||
},
|
||||
overlay: {
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0
|
||||
},
|
||||
inner: {
|
||||
position: "relative",
|
||||
pointerEvents: "none",
|
||||
zIndex: 1
|
||||
},
|
||||
innerLink: {
|
||||
pointerEvents: "all"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class RepositoryEntry extends React.Component<Props> {
|
||||
createLink = (repository: Repository) => {
|
||||
return `/repo/${repository.namespace}/${repository.name}`;
|
||||
};
|
||||
|
||||
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["changesets"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-code-branch"
|
||||
to={repositoryLink + "/changesets"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["sources"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-code"
|
||||
to={repositoryLink + "/sources"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderModifyLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["update"]) {
|
||||
return (
|
||||
<RepositoryEntryLink iconClass="fa-cog" to={repositoryLink + "/edit"} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repository, classes } = this.props;
|
||||
const repositoryLink = this.createLink(repository);
|
||||
return (
|
||||
<div className={classNames("box", "box-link-shadow", classes.outer)}>
|
||||
<Link className={classes.overlay} to={repositoryLink} />
|
||||
<article className={classNames("media", classes.inner)}>
|
||||
<figure className="media-left">
|
||||
<RepositoryAvatar repository={repository} />
|
||||
</figure>
|
||||
<div className="media-content">
|
||||
<div className="content">
|
||||
<p>
|
||||
<strong>{repository.name}</strong>
|
||||
<br />
|
||||
{repository.description}
|
||||
</p>
|
||||
</div>
|
||||
<nav className="level is-mobile">
|
||||
<div className="level-left">
|
||||
{this.renderChangesetsLink(repository, repositoryLink)}
|
||||
{this.renderSourcesLink(repository, repositoryLink)}
|
||||
{this.renderModifyLink(repository, repositoryLink)}
|
||||
</div>
|
||||
<div className="level-right is-hidden-mobile">
|
||||
<small className="level-item">
|
||||
<DateFromNow date={repository.creationDate} />
|
||||
</small>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(RepositoryEntry);
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import injectSheet from "react-jss";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import RepositoryEntryLink from "./RepositoryEntryLink";
|
||||
import classNames from "classnames";
|
||||
import RepositoryAvatar from "./RepositoryAvatar";
|
||||
|
||||
const styles = {
|
||||
overlay: {
|
||||
position: "absolute",
|
||||
height: "calc(120px - 1.5rem)",
|
||||
width: "calc(50% - 3rem)"
|
||||
},
|
||||
inner: {
|
||||
position: "relative",
|
||||
pointerEvents: "none",
|
||||
zIndex: 1
|
||||
},
|
||||
innerLink: {
|
||||
pointerEvents: "all"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class RepositoryEntry extends React.Component<Props> {
|
||||
createLink = (repository: Repository) => {
|
||||
return `/repo/${repository.namespace}/${repository.name}`;
|
||||
};
|
||||
|
||||
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["changesets"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-code-branch fa-lg"
|
||||
to={repositoryLink + "/changesets"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["sources"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-code fa-lg"
|
||||
to={repositoryLink + "/sources"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderModifyLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["update"]) {
|
||||
return (
|
||||
<RepositoryEntryLink iconClass="fa-cog fa-lg" to={repositoryLink + "/edit"} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repository, classes } = this.props;
|
||||
const repositoryLink = this.createLink(repository);
|
||||
return (
|
||||
<div className={classNames("box", "box-link-shadow", "column", "is-half")}>
|
||||
<Link className={classNames(classes.overlay)} to={repositoryLink} />
|
||||
<article className={classNames("media", classes.inner)}>
|
||||
<figure className="media-left">
|
||||
<RepositoryAvatar repository={repository} />
|
||||
</figure>
|
||||
<div className="media-content">
|
||||
<div className="content">
|
||||
<p>
|
||||
<strong>{repository.name}</strong>
|
||||
<br />
|
||||
{repository.description}
|
||||
</p>
|
||||
</div>
|
||||
<nav className="level is-mobile">
|
||||
<div className="level-left">
|
||||
{this.renderChangesetsLink(repository, repositoryLink)}
|
||||
{this.renderSourcesLink(repository, repositoryLink)}
|
||||
{this.renderModifyLink(repository, repositoryLink)}
|
||||
</div>
|
||||
<div className="level-right is-hidden-mobile">
|
||||
<small className="level-item">
|
||||
<DateFromNow date={repository.creationDate} />
|
||||
</small>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(RepositoryEntry);
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
link: {
|
||||
pointerEvents: "all"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
iconClass: string,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class RepositoryEntryLink extends React.Component<Props> {
|
||||
render() {
|
||||
const { to, iconClass, classes } = this.props;
|
||||
return (
|
||||
<Link className={classNames("level-item", classes.link)} to={to}>
|
||||
<span className="icon is-small">
|
||||
<i className={classNames("fa", iconClass)} />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(RepositoryEntryLink);
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
link: {
|
||||
pointerEvents: "all",
|
||||
marginRight: "1.25rem !important"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
iconClass: string,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class RepositoryEntryLink extends React.Component<Props> {
|
||||
render() {
|
||||
const { to, iconClass, classes } = this.props;
|
||||
return (
|
||||
<Link className={classNames("level-item", classes.link)} to={to}>
|
||||
<span className="icon is-small">
|
||||
<i className={classNames("fa", iconClass)} />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(RepositoryEntryLink);
|
||||
|
||||
@@ -12,6 +12,12 @@ const styles = {
|
||||
},
|
||||
repoGroup: {
|
||||
marginBottom: "1em"
|
||||
},
|
||||
wrapper: {
|
||||
padding: "0 0.75rem"
|
||||
},
|
||||
clearfix: {
|
||||
clear: "both"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,7 +65,10 @@ class RepositoryGroupEntry extends React.Component<Props, State> {
|
||||
</span>
|
||||
</h2>
|
||||
<hr />
|
||||
<div className={classNames("columns","is-multiline", classes.wrapper)}>
|
||||
{content}
|
||||
</div>
|
||||
<div className={classes.clearfix}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,87 +1,90 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import type { Branch } from "@scm-manager/ui-types";
|
||||
import DropDown from "../components/DropDown";
|
||||
import { translate } from "react-i18next";
|
||||
import injectSheet from "react-jss";
|
||||
import { compose } from "redux";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
zeroflex: {
|
||||
flexGrow: 0
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
branches: Branch[], // TODO: Use generics?
|
||||
selected: (branch?: Branch) => void,
|
||||
selectedBranch: string,
|
||||
|
||||
// context props
|
||||
classes: Object,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = { selectedBranch?: Branch };
|
||||
|
||||
class BranchSelector extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.branches
|
||||
.filter(branch => branch.name === this.props.selectedBranch)
|
||||
.forEach(branch => this.setState({ selectedBranch: branch }));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { branches, classes, t } = this.props;
|
||||
|
||||
if (branches) {
|
||||
return (
|
||||
<div className="box field is-horizontal">
|
||||
<div
|
||||
className={classNames("field-label", "is-normal", classes.zeroflex)}
|
||||
>
|
||||
<label className="label">{t("branch-selector.label")}</label>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field is-narrow">
|
||||
<div className="control">
|
||||
<DropDown
|
||||
className="is-fullwidth"
|
||||
options={branches.map(b => b.name)}
|
||||
optionSelected={this.branchSelected}
|
||||
preselectedOption={
|
||||
this.state.selectedBranch
|
||||
? this.state.selectedBranch.name
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
branchSelected = (branchName: string) => {
|
||||
const { branches, selected } = this.props;
|
||||
const branch = branches.find(b => b.name === branchName);
|
||||
|
||||
selected(branch);
|
||||
this.setState({ selectedBranch: branch });
|
||||
};
|
||||
}
|
||||
|
||||
export default compose(
|
||||
injectSheet(styles),
|
||||
translate("repos")
|
||||
)(BranchSelector);
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import type { Branch } from "@scm-manager/ui-types";
|
||||
import DropDown from "../components/DropDown";
|
||||
import { translate } from "react-i18next";
|
||||
import injectSheet from "react-jss";
|
||||
import { compose } from "redux";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
zeroflex: {
|
||||
flexGrow: 0
|
||||
},
|
||||
wrapper: {
|
||||
padding: "1rem 1.5rem 0.25rem 1.5rem",
|
||||
border: "1px solid #eee",
|
||||
borderRadius: "5px 5px 0 0"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
branches: Branch[], // TODO: Use generics?
|
||||
selected: (branch?: Branch) => void,
|
||||
selectedBranch: string,
|
||||
|
||||
// context props
|
||||
classes: Object,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = { selectedBranch?: Branch };
|
||||
|
||||
class BranchSelector extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.branches
|
||||
.filter(branch => branch.name === this.props.selectedBranch)
|
||||
.forEach(branch => this.setState({ selectedBranch: branch }));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { branches, classes, t } = this.props;
|
||||
|
||||
if (branches) {
|
||||
return (
|
||||
<div className={classNames("has-background-light field", "is-horizontal", classes.wrapper)}>
|
||||
<div className={classNames("field-label", "is-normal", classes.zeroflex)}>
|
||||
<label className="label">{t("branch-selector.label")}</label>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field is-narrow">
|
||||
<div className="control">
|
||||
<DropDown
|
||||
className="is-fullwidth"
|
||||
options={branches.map(b => b.name)}
|
||||
optionSelected={this.branchSelected}
|
||||
preselectedOption={
|
||||
this.state.selectedBranch
|
||||
? this.state.selectedBranch.name
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
branchSelected = (branchName: string) => {
|
||||
const { branches, selected } = this.props;
|
||||
const branch = branches.find(b => b.name === branchName);
|
||||
|
||||
selected(branch);
|
||||
this.setState({ selectedBranch: branch });
|
||||
};
|
||||
}
|
||||
|
||||
export default compose(
|
||||
injectSheet(styles),
|
||||
translate("repos")
|
||||
)(BranchSelector);
|
||||
|
||||
@@ -1,122 +1,134 @@
|
||||
// @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, PermissionCreateEntry} from "@scm-manager/ui-types";
|
||||
import * as validator from "./permissionValidation";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
createPermission: (permission: PermissionCreateEntry) => 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")}
|
||||
helpText={t("permission.help.nameHelpText")}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("permission.group-permission")}
|
||||
checked={groupPermission ? groupPermission : false}
|
||||
onChange={this.handleGroupPermissionChange}
|
||||
helpText={t("permission.help.groupPermissionHelpText")}
|
||||
/>
|
||||
<TypeSelector
|
||||
label={t("permission.type")}
|
||||
helpText={t("permission.help.typeHelpText")}
|
||||
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);
|
||||
// @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, PermissionCreateEntry} from "@scm-manager/ui-types";
|
||||
import * as validator from "./permissionValidation";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
createPermission: (permission: PermissionCreateEntry) => 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>
|
||||
<hr />
|
||||
<h2 className="subtitle">
|
||||
{t("permission.add-permission.add-permission-heading")}
|
||||
</h2>
|
||||
<form onSubmit={this.submit}>
|
||||
<div class="columns">
|
||||
<div class="column is-three-quarters">
|
||||
<InputField
|
||||
label={t("permission.name")}
|
||||
value={name ? name : ""}
|
||||
onChange={this.handleNameChange}
|
||||
validationError={!this.state.valid}
|
||||
errorMessage={t("permission.add-permission.name-input-invalid")}
|
||||
helpText={t("permission.help.nameHelpText")}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label={t("permission.group-permission")}
|
||||
checked={groupPermission ? groupPermission : false}
|
||||
onChange={this.handleGroupPermissionChange}
|
||||
helpText={t("permission.help.groupPermissionHelpText")}
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-one-quarter">
|
||||
<TypeSelector
|
||||
label={t("permission.type")}
|
||||
helpText={t("permission.help.typeHelpText")}
|
||||
handleTypeChange={this.handleTypeChange}
|
||||
type={type ? type : "READ"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<SubmitButton
|
||||
label={t("permission.add-permission.submit-button")}
|
||||
loading={loading}
|
||||
disabled={!this.state.valid || this.state.name === ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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);
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Select } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
handleTypeChange: string => void,
|
||||
type: string,
|
||||
label?: string,
|
||||
helpText?: string,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
class TypeSelector extends React.Component<Props> {
|
||||
render() {
|
||||
const { type, handleTypeChange, loading, label, helpText } = this.props;
|
||||
const types = ["READ", "OWNER", "WRITE"];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={handleTypeChange}
|
||||
value={type ? type : "READ"}
|
||||
options={this.createSelectOptions(types)}
|
||||
loading={loading}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
createSelectOptions(types: string[]) {
|
||||
return types.map(type => {
|
||||
return {
|
||||
label: type,
|
||||
value: type
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(TypeSelector);
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Select } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
handleTypeChange: string => void,
|
||||
type: string,
|
||||
label?: string,
|
||||
helpText?: string,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
class TypeSelector extends React.Component<Props> {
|
||||
render() {
|
||||
const { type, handleTypeChange, loading, label, helpText } = this.props;
|
||||
const types = ["READ", "OWNER", "WRITE"];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={handleTypeChange}
|
||||
value={type ? type : "READ"}
|
||||
options={this.createSelectOptions(types)}
|
||||
loading={loading}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
createSelectOptions(types: string[]) {
|
||||
return types.map(type => {
|
||||
return {
|
||||
label: type,
|
||||
value: type
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(TypeSelector);
|
||||
|
||||
@@ -1,209 +1,209 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
fetchPermissions,
|
||||
getFetchPermissionsFailure,
|
||||
isFetchPermissionsPending,
|
||||
getPermissionsOfRepo,
|
||||
hasCreatePermission,
|
||||
createPermission,
|
||||
isCreatePermissionPending,
|
||||
getCreatePermissionFailure,
|
||||
createPermissionReset,
|
||||
getDeletePermissionsFailure,
|
||||
getModifyPermissionsFailure,
|
||||
modifyPermissionReset,
|
||||
deletePermissionReset
|
||||
} from "../modules/permissions";
|
||||
import { Loading, ErrorPage } from "@scm-manager/ui-components";
|
||||
import type {
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
} from "@scm-manager/ui-types";
|
||||
import SinglePermission from "./SinglePermission";
|
||||
import CreatePermissionForm from "../components/CreatePermissionForm";
|
||||
import type { History } from "history";
|
||||
import { getPermissionsLink } from "../../modules/repos";
|
||||
|
||||
type Props = {
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
permissions: PermissionCollection,
|
||||
hasPermissionToCreate: boolean,
|
||||
loadingCreatePermission: boolean,
|
||||
permissionsLink: string,
|
||||
|
||||
//dispatch functions
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => void,
|
||||
createPermissionReset: (string, string) => void,
|
||||
modifyPermissionReset: (string, string) => void,
|
||||
deletePermissionReset: (string, string) => void,
|
||||
// context props
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
class Permissions extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const {
|
||||
fetchPermissions,
|
||||
namespace,
|
||||
repoName,
|
||||
modifyPermissionReset,
|
||||
createPermissionReset,
|
||||
deletePermissionReset,
|
||||
permissionsLink
|
||||
} = this.props;
|
||||
|
||||
createPermissionReset(namespace, repoName);
|
||||
modifyPermissionReset(namespace, repoName);
|
||||
deletePermissionReset(namespace, repoName);
|
||||
fetchPermissions(permissionsLink, namespace, repoName);
|
||||
}
|
||||
|
||||
createPermission = (permission: Permission) => {
|
||||
this.props.createPermission(
|
||||
this.props.permissionsLink,
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
permissions,
|
||||
t,
|
||||
namespace,
|
||||
repoName,
|
||||
loadingCreatePermission,
|
||||
hasPermissionToCreate
|
||||
} = 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>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{permissions.map(permission => {
|
||||
return (
|
||||
<SinglePermission
|
||||
key={permission.name + permission.groupPermission.toString()}
|
||||
namespace={namespace}
|
||||
repoName={repoName}
|
||||
permission={permission}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{createPermissionForm}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const namespace = ownProps.namespace;
|
||||
const repoName = ownProps.repoName;
|
||||
const error =
|
||||
getFetchPermissionsFailure(state, namespace, repoName) ||
|
||||
getCreatePermissionFailure(state, namespace, repoName) ||
|
||||
getDeletePermissionsFailure(state, namespace, repoName) ||
|
||||
getModifyPermissionsFailure(state, namespace, repoName);
|
||||
const loading = isFetchPermissionsPending(state, namespace, repoName);
|
||||
const permissions = getPermissionsOfRepo(state, namespace, repoName);
|
||||
const loadingCreatePermission = isCreatePermissionPending(
|
||||
state,
|
||||
namespace,
|
||||
repoName
|
||||
);
|
||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||
return {
|
||||
namespace,
|
||||
repoName,
|
||||
error,
|
||||
loading,
|
||||
permissions,
|
||||
hasPermissionToCreate,
|
||||
loadingCreatePermission,
|
||||
permissionsLink
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => {
|
||||
dispatch(fetchPermissions(link, namespace, repoName));
|
||||
},
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => {
|
||||
dispatch(createPermission(link, permission, namespace, repoName, callback));
|
||||
},
|
||||
createPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(createPermissionReset(namespace, repoName));
|
||||
},
|
||||
modifyPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(modifyPermissionReset(namespace, repoName));
|
||||
},
|
||||
deletePermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(deletePermissionReset(namespace, repoName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(Permissions));
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
fetchPermissions,
|
||||
getFetchPermissionsFailure,
|
||||
isFetchPermissionsPending,
|
||||
getPermissionsOfRepo,
|
||||
hasCreatePermission,
|
||||
createPermission,
|
||||
isCreatePermissionPending,
|
||||
getCreatePermissionFailure,
|
||||
createPermissionReset,
|
||||
getDeletePermissionsFailure,
|
||||
getModifyPermissionsFailure,
|
||||
modifyPermissionReset,
|
||||
deletePermissionReset
|
||||
} from "../modules/permissions";
|
||||
import { Loading, ErrorPage } from "@scm-manager/ui-components";
|
||||
import type {
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
} from "@scm-manager/ui-types";
|
||||
import SinglePermission from "./SinglePermission";
|
||||
import CreatePermissionForm from "../components/CreatePermissionForm";
|
||||
import type { History } from "history";
|
||||
import { getPermissionsLink } from "../../modules/repos";
|
||||
|
||||
type Props = {
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
permissions: PermissionCollection,
|
||||
hasPermissionToCreate: boolean,
|
||||
loadingCreatePermission: boolean,
|
||||
permissionsLink: string,
|
||||
|
||||
//dispatch functions
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => void,
|
||||
createPermissionReset: (string, string) => void,
|
||||
modifyPermissionReset: (string, string) => void,
|
||||
deletePermissionReset: (string, string) => void,
|
||||
// context props
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
class Permissions extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const {
|
||||
fetchPermissions,
|
||||
namespace,
|
||||
repoName,
|
||||
modifyPermissionReset,
|
||||
createPermissionReset,
|
||||
deletePermissionReset,
|
||||
permissionsLink
|
||||
} = this.props;
|
||||
|
||||
createPermissionReset(namespace, repoName);
|
||||
modifyPermissionReset(namespace, repoName);
|
||||
deletePermissionReset(namespace, repoName);
|
||||
fetchPermissions(permissionsLink, namespace, repoName);
|
||||
}
|
||||
|
||||
createPermission = (permission: Permission) => {
|
||||
this.props.createPermission(
|
||||
this.props.permissionsLink,
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
permissions,
|
||||
t,
|
||||
namespace,
|
||||
repoName,
|
||||
loadingCreatePermission,
|
||||
hasPermissionToCreate
|
||||
} = 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="has-background-light table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("permission.name")}</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("permission.group-permission")}
|
||||
</th>
|
||||
<th>{t("permission.type")}</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{permissions.map(permission => {
|
||||
return (
|
||||
<SinglePermission
|
||||
key={permission.name + permission.groupPermission.toString()}
|
||||
namespace={namespace}
|
||||
repoName={repoName}
|
||||
permission={permission}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{createPermissionForm}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const namespace = ownProps.namespace;
|
||||
const repoName = ownProps.repoName;
|
||||
const error =
|
||||
getFetchPermissionsFailure(state, namespace, repoName) ||
|
||||
getCreatePermissionFailure(state, namespace, repoName) ||
|
||||
getDeletePermissionsFailure(state, namespace, repoName) ||
|
||||
getModifyPermissionsFailure(state, namespace, repoName);
|
||||
const loading = isFetchPermissionsPending(state, namespace, repoName);
|
||||
const permissions = getPermissionsOfRepo(state, namespace, repoName);
|
||||
const loadingCreatePermission = isCreatePermissionPending(
|
||||
state,
|
||||
namespace,
|
||||
repoName
|
||||
);
|
||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||
return {
|
||||
namespace,
|
||||
repoName,
|
||||
error,
|
||||
loading,
|
||||
permissions,
|
||||
hasPermissionToCreate,
|
||||
loadingCreatePermission,
|
||||
permissionsLink
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => {
|
||||
dispatch(fetchPermissions(link, namespace, repoName));
|
||||
},
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => {
|
||||
dispatch(createPermission(link, permission, namespace, repoName, callback));
|
||||
},
|
||||
createPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(createPermissionReset(namespace, repoName));
|
||||
},
|
||||
modifyPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(modifyPermissionReset(namespace, repoName));
|
||||
},
|
||||
deletePermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(deletePermissionReset(namespace, repoName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(Permissions));
|
||||
|
||||
@@ -1,176 +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));
|
||||
// @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));
|
||||
|
||||
@@ -1,184 +1,198 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Checkbox,
|
||||
InputField,
|
||||
PasswordConfirmation,
|
||||
SubmitButton,
|
||||
validation as validator
|
||||
} from "@scm-manager/ui-components";
|
||||
import * as userValidator from "./userValidation";
|
||||
|
||||
type Props = {
|
||||
submitForm: User => void,
|
||||
user?: User,
|
||||
loading?: boolean,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = {
|
||||
user: User,
|
||||
mailValidationError: boolean,
|
||||
nameValidationError: boolean,
|
||||
displayNameValidationError: boolean
|
||||
};
|
||||
|
||||
class UserForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
user: {
|
||||
name: "",
|
||||
displayName: "",
|
||||
mail: "",
|
||||
password: "",
|
||||
admin: false,
|
||||
active: true,
|
||||
_links: {}
|
||||
},
|
||||
mailValidationError: false,
|
||||
displayNameValidationError: false,
|
||||
nameValidationError: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { user } = this.props;
|
||||
if (user) {
|
||||
this.setState({ user: { ...user } });
|
||||
}
|
||||
}
|
||||
|
||||
isFalsy(value) {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isValid = () => {
|
||||
const user = this.state.user;
|
||||
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
|
||||
return !(
|
||||
this.state.nameValidationError ||
|
||||
this.state.mailValidationError ||
|
||||
this.state.displayNameValidationError ||
|
||||
this.isFalsy(user.name) ||
|
||||
this.isFalsy(user.displayName) ||
|
||||
this.isFalsy(user.mail) ||
|
||||
passwordValid
|
||||
);
|
||||
};
|
||||
|
||||
submit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (this.isValid()) {
|
||||
this.props.submitForm(this.state.user);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, t } = this.props;
|
||||
const user = this.state.user;
|
||||
|
||||
let nameField = null;
|
||||
let passwordChangeField = null;
|
||||
if (!this.props.user) {
|
||||
nameField = (
|
||||
<InputField
|
||||
label={t("user.name")}
|
||||
onChange={this.handleUsernameChange}
|
||||
value={user ? user.name : ""}
|
||||
validationError={this.state.nameValidationError}
|
||||
errorMessage={t("validation.name-invalid")}
|
||||
helpText={t("help.usernameHelpText")}
|
||||
/>
|
||||
);
|
||||
|
||||
passwordChangeField = (
|
||||
<PasswordConfirmation passwordChanged={this.handlePasswordChange} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<form onSubmit={this.submit}>
|
||||
{nameField}
|
||||
<InputField
|
||||
label={t("user.displayName")}
|
||||
onChange={this.handleDisplayNameChange}
|
||||
value={user ? user.displayName : ""}
|
||||
validationError={this.state.displayNameValidationError}
|
||||
errorMessage={t("validation.displayname-invalid")}
|
||||
helpText={t("help.displayNameHelpText")}
|
||||
/>
|
||||
<InputField
|
||||
label={t("user.mail")}
|
||||
onChange={this.handleEmailChange}
|
||||
value={user ? user.mail : ""}
|
||||
validationError={this.state.mailValidationError}
|
||||
errorMessage={t("validation.mail-invalid")}
|
||||
helpText={t("help.mailHelpText")}
|
||||
/>
|
||||
{passwordChangeField}
|
||||
<Checkbox
|
||||
label={t("user.admin")}
|
||||
onChange={this.handleAdminChange}
|
||||
checked={user ? user.admin : false}
|
||||
helpText={t("help.adminHelpText")}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("user.active")}
|
||||
onChange={this.handleActiveChange}
|
||||
checked={user ? user.active : false}
|
||||
helpText={t("help.activeHelpText")}
|
||||
/>
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("user-form.submit")}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
handleUsernameChange = (name: string) => {
|
||||
this.setState({
|
||||
nameValidationError: !validator.isNameValid(name),
|
||||
user: { ...this.state.user, name }
|
||||
});
|
||||
};
|
||||
|
||||
handleDisplayNameChange = (displayName: string) => {
|
||||
this.setState({
|
||||
displayNameValidationError: !userValidator.isDisplayNameValid(
|
||||
displayName
|
||||
),
|
||||
user: { ...this.state.user, displayName }
|
||||
});
|
||||
};
|
||||
|
||||
handleEmailChange = (mail: string) => {
|
||||
this.setState({
|
||||
mailValidationError: !validator.isMailValid(mail),
|
||||
user: { ...this.state.user, mail }
|
||||
});
|
||||
};
|
||||
|
||||
handlePasswordChange = (password: string) => {
|
||||
this.setState({
|
||||
user: { ...this.state.user, password }
|
||||
});
|
||||
};
|
||||
|
||||
handleAdminChange = (admin: boolean) => {
|
||||
this.setState({ user: { ...this.state.user, admin } });
|
||||
};
|
||||
|
||||
handleActiveChange = (active: boolean) => {
|
||||
this.setState({ user: { ...this.state.user, active } });
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("users")(UserForm);
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Checkbox,
|
||||
InputField,
|
||||
PasswordConfirmation,
|
||||
SubmitButton,
|
||||
validation as validator
|
||||
} from "@scm-manager/ui-components";
|
||||
import * as userValidator from "./userValidation";
|
||||
|
||||
type Props = {
|
||||
submitForm: User => void,
|
||||
user?: User,
|
||||
loading?: boolean,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = {
|
||||
user: User,
|
||||
mailValidationError: boolean,
|
||||
nameValidationError: boolean,
|
||||
displayNameValidationError: boolean
|
||||
};
|
||||
|
||||
class UserForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
user: {
|
||||
name: "",
|
||||
displayName: "",
|
||||
mail: "",
|
||||
password: "",
|
||||
admin: false,
|
||||
active: true,
|
||||
_links: {}
|
||||
},
|
||||
mailValidationError: false,
|
||||
displayNameValidationError: false,
|
||||
nameValidationError: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { user } = this.props;
|
||||
if (user) {
|
||||
this.setState({ user: { ...user } });
|
||||
}
|
||||
}
|
||||
|
||||
isFalsy(value) {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isValid = () => {
|
||||
const user = this.state.user;
|
||||
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
|
||||
return !(
|
||||
this.state.nameValidationError ||
|
||||
this.state.mailValidationError ||
|
||||
this.state.displayNameValidationError ||
|
||||
this.isFalsy(user.name) ||
|
||||
this.isFalsy(user.displayName) ||
|
||||
this.isFalsy(user.mail) ||
|
||||
passwordValid
|
||||
);
|
||||
};
|
||||
|
||||
submit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (this.isValid()) {
|
||||
this.props.submitForm(this.state.user);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, t } = this.props;
|
||||
const user = this.state.user;
|
||||
|
||||
let nameField = null;
|
||||
let passwordChangeField = null;
|
||||
if (!this.props.user) {
|
||||
nameField = (
|
||||
<InputField
|
||||
label={t("user.name")}
|
||||
onChange={this.handleUsernameChange}
|
||||
value={user ? user.name : ""}
|
||||
validationError={this.state.nameValidationError}
|
||||
errorMessage={t("validation.name-invalid")}
|
||||
helpText={t("help.usernameHelpText")}
|
||||
/>
|
||||
);
|
||||
|
||||
passwordChangeField = (
|
||||
<PasswordConfirmation passwordChanged={this.handlePasswordChange} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<form onSubmit={this.submit}>
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
{nameField}
|
||||
<InputField
|
||||
label={t("user.displayName")}
|
||||
onChange={this.handleDisplayNameChange}
|
||||
value={user ? user.displayName : ""}
|
||||
validationError={this.state.displayNameValidationError}
|
||||
errorMessage={t("validation.displayname-invalid")}
|
||||
helpText={t("help.displayNameHelpText")}
|
||||
/>
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<InputField
|
||||
label={t("user.mail")}
|
||||
onChange={this.handleEmailChange}
|
||||
value={user ? user.mail : ""}
|
||||
validationError={this.state.mailValidationError}
|
||||
errorMessage={t("validation.mail-invalid")}
|
||||
helpText={t("help.mailHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
{passwordChangeField}
|
||||
<Checkbox
|
||||
label={t("user.admin")}
|
||||
onChange={this.handleAdminChange}
|
||||
checked={user ? user.admin : false}
|
||||
helpText={t("help.adminHelpText")}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("user.active")}
|
||||
onChange={this.handleActiveChange}
|
||||
checked={user ? user.active : false}
|
||||
helpText={t("help.activeHelpText")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("user-form.submit")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
handleUsernameChange = (name: string) => {
|
||||
this.setState({
|
||||
nameValidationError: !validator.isNameValid(name),
|
||||
user: { ...this.state.user, name }
|
||||
});
|
||||
};
|
||||
|
||||
handleDisplayNameChange = (displayName: string) => {
|
||||
this.setState({
|
||||
displayNameValidationError: !userValidator.isDisplayNameValid(
|
||||
displayName
|
||||
),
|
||||
user: { ...this.state.user, displayName }
|
||||
});
|
||||
};
|
||||
|
||||
handleEmailChange = (mail: string) => {
|
||||
this.setState({
|
||||
mailValidationError: !validator.isMailValid(mail),
|
||||
user: { ...this.state.user, mail }
|
||||
});
|
||||
};
|
||||
|
||||
handlePasswordChange = (password: string) => {
|
||||
this.setState({
|
||||
user: { ...this.state.user, password }
|
||||
});
|
||||
};
|
||||
|
||||
handleAdminChange = (admin: boolean) => {
|
||||
this.setState({ user: { ...this.state.user, admin } });
|
||||
};
|
||||
|
||||
handleActiveChange = (active: boolean) => {
|
||||
this.setState({ user: { ...this.state.user, active } });
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("users")(UserForm);
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import { Checkbox, MailLink, DateFromNow } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
user: User,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class Details extends React.Component<Props> {
|
||||
render() {
|
||||
const { user, t } = this.props;
|
||||
return (
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("user.name")}</td>
|
||||
<td>{user.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.displayName")}</td>
|
||||
<td>{user.displayName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.mail")}</td>
|
||||
<td>
|
||||
<MailLink address={user.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.admin")}</td>
|
||||
<td>
|
||||
<Checkbox checked={user.admin} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.active")}</td>
|
||||
<td>
|
||||
<Checkbox checked={user.active} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.type")}</td>
|
||||
<td>{user.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.creationDate")}</td>
|
||||
<td>
|
||||
<DateFromNow date={user.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("user.lastModified")}</td>
|
||||
<td>
|
||||
<DateFromNow date={user.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("users")(Details);
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import { Checkbox, MailLink, DateFromNow } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
user: User,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class Details extends React.Component<Props> {
|
||||
render() {
|
||||
const { user, t } = this.props;
|
||||
return (
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.name")}</td>
|
||||
<td>{user.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.displayName")}</td>
|
||||
<td>{user.displayName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.mail")}</td>
|
||||
<td>
|
||||
<MailLink address={user.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.admin")}</td>
|
||||
<td>
|
||||
<Checkbox checked={user.admin} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.active")}</td>
|
||||
<td>
|
||||
<Checkbox checked={user.active} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.type")}</td>
|
||||
<td>{user.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.creationDate")}</td>
|
||||
<td>
|
||||
<DateFromNow date={user.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("user.lastModified")}</td>
|
||||
<td>
|
||||
<DateFromNow date={user.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("users")(Details);
|
||||
|
||||
@@ -61,6 +61,123 @@ $fa-font-path: "webfonts";
|
||||
|
||||
// NEW STYLES
|
||||
|
||||
//typography
|
||||
.subtitle {
|
||||
color: #666;
|
||||
}
|
||||
.has-border-white {
|
||||
border-color: #fff !important;
|
||||
}
|
||||
// buttons
|
||||
.button{
|
||||
padding-left: 1.5em;
|
||||
padding-right: 1.5em;
|
||||
height:2.5rem;
|
||||
|
||||
&.is-primary {
|
||||
background-color: $mint;
|
||||
}
|
||||
}
|
||||
|
||||
// multiline Columns
|
||||
.columns.is-multiline {
|
||||
|
||||
.column.is-half{
|
||||
width: calc(50% - .75rem);
|
||||
|
||||
&:nth-child(odd){
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width:768px) {
|
||||
.column.is-half{
|
||||
width: 100%;
|
||||
|
||||
&:nth-child(odd){
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tables
|
||||
.table {
|
||||
width: 100%;
|
||||
td {
|
||||
border-color: #eee;
|
||||
padding: 1rem
|
||||
}
|
||||
}
|
||||
|
||||
// card tables
|
||||
.card-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px 5px;
|
||||
|
||||
tr{
|
||||
a{
|
||||
color: #363636;
|
||||
}
|
||||
&:hover {
|
||||
td {
|
||||
background-color: whitesmoke;
|
||||
&:nth-child(4){
|
||||
background-color: #E1E1E1;
|
||||
}
|
||||
}
|
||||
a{
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
td {
|
||||
border-bottom: 1px solid whitesmoke;
|
||||
background-color: #fafafa;
|
||||
padding: 1em 1.25em;
|
||||
&:first-child{
|
||||
border-left: 3px solid $mint;
|
||||
}
|
||||
&:nth-child(4){
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
}
|
||||
&.is-hoverable tbody tr:not(.is-selected):hover {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
thead th {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
// forms
|
||||
.field:not(.is-grouped){
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.input, .textarea {
|
||||
/*background-color: whitesmoke;*/
|
||||
border-color: #98d8f3;
|
||||
box-shadow: none;
|
||||
}
|
||||
/*.input[disabled], .textarea[disabled] {
|
||||
|
||||
background-color: #ddd;
|
||||
border-color: #ccc;
|
||||
box-shadow: none;
|
||||
color: #aaa;
|
||||
}*/
|
||||
|
||||
// pagination
|
||||
.pagination-next, .pagination-link, .pagination-ellipsis{
|
||||
padding-left: 1.5em;
|
||||
padding-right: 1.5em;
|
||||
height:2.5rem;
|
||||
}
|
||||
.pagination-previous, .pagination-next {
|
||||
min-width: 6.75em;
|
||||
}
|
||||
|
||||
// dark hero colors
|
||||
.hero.is-dark {
|
||||
background-color: #002e4b;
|
||||
@@ -77,33 +194,13 @@ $fa-font-path: "webfonts";
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
// footer colors
|
||||
|
||||
|
||||
// footer
|
||||
.footer {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
//typography
|
||||
.subtitle {
|
||||
color: #666;
|
||||
}
|
||||
// buttons
|
||||
.button{
|
||||
padding-left: 1.5em;
|
||||
padding-right: 1.5em;
|
||||
height:2.5rem;
|
||||
|
||||
&.is-primary {
|
||||
background-color: $mint;
|
||||
}
|
||||
}
|
||||
// pagination
|
||||
.pagination-next, .pagination-link, .pagination-ellipsis{
|
||||
padding-left: 1.5em;
|
||||
padding-right: 1.5em;
|
||||
height:2.5rem;
|
||||
}
|
||||
.pagination-previous, .pagination-next {
|
||||
min-width: 6.75em;
|
||||
}
|
||||
|
||||
// sidebar menu
|
||||
.aside-background {
|
||||
|
||||
@@ -150,13 +247,13 @@ $fa-font-path: "webfonts";
|
||||
border-right: 1px solid #eee;
|
||||
|
||||
&.is-active {
|
||||
color: #33B2E8;
|
||||
color: $blue;
|
||||
background-color: #fff;
|
||||
|
||||
&:before{
|
||||
position: relative;
|
||||
content: " ";
|
||||
background: #33B2E8;
|
||||
background: $blue;
|
||||
height: 53px;
|
||||
width: 2px;
|
||||
display: block;
|
||||
@@ -173,69 +270,4 @@ $fa-font-path: "webfonts";
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
// tables
|
||||
.table {
|
||||
width: 100%;
|
||||
td {
|
||||
border-color: #eee;
|
||||
padding: 1rem
|
||||
}
|
||||
}
|
||||
// card tables
|
||||
.card-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px 5px;
|
||||
|
||||
tr{
|
||||
a{
|
||||
color: #363636;
|
||||
}
|
||||
&:hover {
|
||||
td {
|
||||
background-color: whitesmoke;
|
||||
&:nth-child(4){
|
||||
background-color: #E1E1E1;
|
||||
}
|
||||
}
|
||||
a{
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
td {
|
||||
border-bottom: 1px solid whitesmoke;
|
||||
background-color: #fafafa;
|
||||
padding: 1em 1.25em;
|
||||
&:first-child{
|
||||
border-left: 3px solid $mint;
|
||||
}
|
||||
&:nth-child(4){
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
}
|
||||
&.is-hoverable tbody tr:not(.is-selected):hover {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
thead th {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
// forms
|
||||
.field:not(.is-grouped){
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.input, .textarea {
|
||||
background-color: whitesmoke;
|
||||
border-color: #efefef;
|
||||
box-shadow: none;
|
||||
}
|
||||
.input[disabled], .textarea[disabled] {
|
||||
|
||||
background-color: #ddd;
|
||||
border-color: #ccc;
|
||||
box-shadow: none;
|
||||
color: #aaa;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user