mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-07 22:15:45 +01:00
Merged in feature/improved-navi (pull request #152)
Feature/improved navi
This commit is contained in:
@@ -127,6 +127,7 @@ class RepositoryConfig extends React.Component<Props, State> {
|
||||
disabled={!this.state.selectedBranchName}
|
||||
/>
|
||||
</form>
|
||||
<hr />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -27,14 +27,9 @@ binder.bind(
|
||||
);
|
||||
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
|
||||
|
||||
cfgBinder.bindRepository(
|
||||
"/configuration",
|
||||
"scm-git-plugin.repo-config.link",
|
||||
"configuration",
|
||||
RepositoryConfig
|
||||
);
|
||||
// global config
|
||||
binder.bind("repo-config.route", RepositoryConfig, gitPredicate);
|
||||
|
||||
// global config
|
||||
cfgBinder.bindGlobal(
|
||||
"/git",
|
||||
"scm-git-plugin.config.link",
|
||||
|
||||
@@ -72,6 +72,33 @@ class ConfigurationBinder {
|
||||
binder.bind("repository.route", RepoRoute, repoPredicate);
|
||||
}
|
||||
|
||||
bindRepositorySetting(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
|
||||
|
||||
// create predicate based on the link name of the current repository route
|
||||
// if the linkname is not available, the navigation link and the route are not bound to the extension points
|
||||
const repoPredicate = (props: Object) => {
|
||||
return props.repository && props.repository._links && props.repository._links[linkName];
|
||||
};
|
||||
|
||||
// create NavigationLink with translated label
|
||||
const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => {
|
||||
return this.navLink(url + "/settings" + to, labelI18nKey, t);
|
||||
});
|
||||
|
||||
// bind navigation link to extension point
|
||||
binder.bind("repository.subnavigation", RepoNavLink, repoPredicate);
|
||||
|
||||
|
||||
// route for global configuration, passes the current repository to component
|
||||
const RepoRoute = ({url, repository, ...additionalProps}) => {
|
||||
const link = repository._links[linkName].href;
|
||||
return this.route(url + "/settings" + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
binder.bind("repository.route", RepoRoute, repoPredicate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ConfigurationBinder();
|
||||
|
||||
@@ -28,7 +28,7 @@ class NavLink extends React.Component<Props> {
|
||||
|
||||
let showIcon = null;
|
||||
if (icon) {
|
||||
showIcon = (<><i className={icon}></i>{" "}</>);
|
||||
showIcon = (<><i className={icon} />{" "}</>);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import { Link, Route } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
icon?: string,
|
||||
label: string,
|
||||
activeOnlyWhenExact?: boolean,
|
||||
activeWhenMatch?: (route: any) => boolean,
|
||||
children?: React.Node
|
||||
};
|
||||
|
||||
class SubNavigation extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
activeOnlyWhenExact: false
|
||||
};
|
||||
|
||||
isActive(route: any) {
|
||||
const { activeWhenMatch } = this.props;
|
||||
return route.match || (activeWhenMatch && activeWhenMatch(route));
|
||||
}
|
||||
|
||||
renderLink = (route: any) => {
|
||||
const { to, icon, label } = this.props;
|
||||
|
||||
let defaultIcon = "fas fa-cog";
|
||||
if (icon) {
|
||||
defaultIcon = icon;
|
||||
}
|
||||
|
||||
let children = null;
|
||||
if (this.isActive(route)) {
|
||||
children = <ul className="sub-menu">{this.props.children}</ul>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link className={this.isActive(route) ? "is-active" : ""} to={to}>
|
||||
<i className={defaultIcon} /> {label}
|
||||
</Link>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { to, activeOnlyWhenExact } = this.props;
|
||||
|
||||
// removes last part of url
|
||||
let parents = to.split("/");
|
||||
parents.splice(-1, 1);
|
||||
let parent = parents.join("/");
|
||||
|
||||
return (
|
||||
<Route
|
||||
path={parent}
|
||||
exact={activeOnlyWhenExact}
|
||||
children={this.renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SubNavigation;
|
||||
@@ -3,6 +3,7 @@
|
||||
export { default as NavAction } from "./NavAction.js";
|
||||
export { default as NavLink } from "./NavLink.js";
|
||||
export { default as Navigation } from "./Navigation.js";
|
||||
export { default as SubNavigation } from "./SubNavigation.js";
|
||||
export { default as PrimaryNavigation } from "./PrimaryNavigation.js";
|
||||
export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js";
|
||||
export { default as Section } from "./Section.js";
|
||||
|
||||
@@ -25,7 +25,7 @@ class ChangesetDiff extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset, t } = this.props;
|
||||
if (!this.isDiffSupported(changeset)) {
|
||||
return <Notification type="danger">{t("changesets.diff.not-supported")}</Notification>;
|
||||
return <Notification type="danger">{t("changesets.changeset.diffNotSupported")}</Notification>;
|
||||
} else {
|
||||
const url = this.createUrl(changeset);
|
||||
return <LoadingDiff url={url} />;
|
||||
|
||||
@@ -43,8 +43,10 @@
|
||||
"previous": "Zurück"
|
||||
},
|
||||
"profile": {
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Aktionen",
|
||||
"navigationLabel": "Profil Navigation",
|
||||
"informationNavLink": "Information",
|
||||
"changePasswordNavLink": "Passwort ändern",
|
||||
"settingsNavLink": "Einstellungen",
|
||||
"username": "Benutzername",
|
||||
"displayName": "Anzeigename",
|
||||
"mail": "E-Mail",
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"navigation-title": "Navigation"
|
||||
},
|
||||
"global-config": {
|
||||
"navigationLabel": "Einstellungs Navigation",
|
||||
"globalConfigurationNavLink": "Globale Einstellungen",
|
||||
"title": "Einstellungen",
|
||||
"navigation-label": "Globale Einstellungen",
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Unbekannter Einstellungen Fehler"
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Einstellungen Fehler"
|
||||
},
|
||||
"config-form": {
|
||||
"submit": "Speichern",
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
"title": "Gruppen",
|
||||
"subtitle": "Verwaltung der Gruppen"
|
||||
},
|
||||
"single-group": {
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Unbekannter Gruppen Fehler",
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Aktionen",
|
||||
"information-label": "Informationen",
|
||||
"back-label": "Zurück"
|
||||
"singleGroup": {
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Gruppen Fehler",
|
||||
"menu": {
|
||||
"navigationLabel": "Gruppen Navigation",
|
||||
"informationNavLink": "Informationen",
|
||||
"settingsNavLink": "Einstellungen",
|
||||
"generalNavLink": "Generell",
|
||||
"setPermissionsNavLink": "Berechtigungen"
|
||||
}
|
||||
},
|
||||
"add-group": {
|
||||
"title": "Gruppe erstellen",
|
||||
@@ -44,27 +47,25 @@
|
||||
"loading": "Suche...",
|
||||
"no-options": "Kein Vorschlag für Benutzername verfügbar"
|
||||
},
|
||||
|
||||
"group-form": {
|
||||
"groupForm": {
|
||||
"subtitle": "Gruppe bearbeiten",
|
||||
"submit": "Speichern",
|
||||
"name-error": "Name ist ungültig",
|
||||
"description-error": "Beschreibung ist ungültig",
|
||||
"nameError": "Name ist ungültig",
|
||||
"descriptionError": "Beschreibung ist ungültig",
|
||||
"help": {
|
||||
"nameHelpText": "Eindeutiger Name der Gruppe",
|
||||
"descriptionHelpText": "Eine kurze Beschreibung der Gruppe",
|
||||
"memberHelpText": "Benutzername des Mitglieds der Gruppe"
|
||||
}
|
||||
},
|
||||
"delete-group-button": {
|
||||
"label": "Löschen",
|
||||
"confirm-alert": {
|
||||
"deleteGroup": {
|
||||
"subtitle": "Gruppe löschen",
|
||||
"button": "Löschen",
|
||||
"confirmAlert": {
|
||||
"title": "Gruppe löschen",
|
||||
"message": "Soll die Gruppe wirklich gelöscht werden?",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
}
|
||||
},
|
||||
"set-permissions-button": {
|
||||
"label": "Berechtigungen ändern"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"form": {
|
||||
"submit-button": {
|
||||
"label": "Berechtigungen speichern"
|
||||
},
|
||||
"set-permissions-successful": "Berechtigungen erfolgreich gespeichert"
|
||||
"setPermissions": {
|
||||
"button": "Berechtigungen speichern",
|
||||
"setPermissionsSuccessful": "Berechtigungen erfolgreich gespeichert"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,41 +11,55 @@
|
||||
"name-invalid": "Der Name des Repository ist ungültig",
|
||||
"contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein"
|
||||
},
|
||||
"help": {
|
||||
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
|
||||
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
|
||||
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
|
||||
"descriptionHelpText": "Eine kurze Beschreibung des Repository."
|
||||
},
|
||||
"repositoryRoot": {
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Repository Fehler",
|
||||
"menu": {
|
||||
"navigationLabel": "Repository Navigation",
|
||||
"informationNavLink": "Informationen",
|
||||
"historyNavLink": "Commits",
|
||||
"sourcesNavLink": "Sources",
|
||||
"settingsNavLink": "Einstellungen",
|
||||
"generalNavLink": "Generell",
|
||||
"permissionsNavLink": "Berechtigungen"
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"title": "Repositories",
|
||||
"subtitle": "Übersicht aller verfügbaren Repositories",
|
||||
"create-button": "Repository erstellen"
|
||||
},
|
||||
"repository-root": {
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Unbekannter Repository Fehler",
|
||||
"actions-label": "Aktionen",
|
||||
"back-label": "Zurück",
|
||||
"navigation-label": "Navigation",
|
||||
"history": "Commits",
|
||||
"information": "Informationen",
|
||||
"permissions": "Berechtigungen",
|
||||
"sources": "Sources"
|
||||
"createButton": "Repository erstellen"
|
||||
},
|
||||
"create": {
|
||||
"title": "Repository erstellen",
|
||||
"subtitle": "Erstellen eines neuen Repository"
|
||||
},
|
||||
"repository-form": {
|
||||
"submit": "Speichern"
|
||||
"changesets": {
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Changesets konnten nicht abgerufen werden",
|
||||
"branchSelectorLabel": "Branches",
|
||||
"changeset": {
|
||||
"description": "Beschreibung",
|
||||
"summary": "Changeset {{id}} wurde committet {{time}}",
|
||||
"diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt",
|
||||
"id": "ID",
|
||||
"contact": "Kontakt",
|
||||
"date": "Datum"
|
||||
},
|
||||
"edit-nav-link": {
|
||||
"label": "Bearbeiten"
|
||||
},
|
||||
"delete-nav-action": {
|
||||
"label": "Löschen",
|
||||
"confirm-alert": {
|
||||
"title": "Repository löschen",
|
||||
"message": "Soll das Repository wirklich gelöscht werden?",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
"author": {
|
||||
"name": "Autor",
|
||||
"mail": "Mail"
|
||||
}
|
||||
},
|
||||
"repositoryForm": {
|
||||
"subtitle": "Repository bearbeiten",
|
||||
"submit": "Speichern"
|
||||
},
|
||||
"sources": {
|
||||
"file-tree": {
|
||||
"name": "Name",
|
||||
@@ -65,27 +79,6 @@
|
||||
"size": "Größe"
|
||||
}
|
||||
},
|
||||
"changesets": {
|
||||
"diff": {
|
||||
"not-supported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt"
|
||||
},
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Changesets konnten nicht abgerufen werden",
|
||||
"changeset": {
|
||||
"id": "ID",
|
||||
"description": "Beschreibung",
|
||||
"contact": "Kontakt",
|
||||
"date": "Datum",
|
||||
"summary": "Changeset {{id}} wurde committet {{time}}"
|
||||
},
|
||||
"author": {
|
||||
"name": "Autor",
|
||||
"mail": "Mail"
|
||||
}
|
||||
},
|
||||
"branch-selector": {
|
||||
"label": "Branches"
|
||||
},
|
||||
"permission": {
|
||||
"user": "Benutzer",
|
||||
"group": "Gruppe",
|
||||
@@ -98,7 +91,7 @@
|
||||
"user-permission": "Benutzerberechtigung",
|
||||
"edit-permission": {
|
||||
"delete-button": "Löschen",
|
||||
"save-button": "Änderungen Speichern"
|
||||
"save-button": "Änderungen speichern"
|
||||
},
|
||||
"advanced-button": {
|
||||
"label": "Erweitert"
|
||||
@@ -138,10 +131,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
|
||||
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
|
||||
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
|
||||
"descriptionHelpText": "Eine kurze Beschreibung des Repository."
|
||||
"deleteRepo": {
|
||||
"subtitle": "Repository löschen",
|
||||
"button": "Löschen",
|
||||
"confirmAlert": {
|
||||
"title": "Repository löschen",
|
||||
"message": "Soll das Repository wirklich gelöscht werden?",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,59 +10,55 @@
|
||||
"creationDate": "Erstellt",
|
||||
"lastModified": "Zuletzt bearbeitet"
|
||||
},
|
||||
"users": {
|
||||
"title": "Benutzer",
|
||||
"subtitle": "Verwaltung der Benutzer"
|
||||
},
|
||||
"create-user-button": {
|
||||
"label": "Benutzer erstellen"
|
||||
},
|
||||
"delete-user-button": {
|
||||
"label": "Löschen",
|
||||
"confirm-alert": {
|
||||
"title": "Benutzer löschen",
|
||||
"message": "Soll der Benutzer wirklich gelöscht werden?",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
}
|
||||
},
|
||||
"edit-user-button": {
|
||||
"label": "Bearbeiten"
|
||||
},
|
||||
"set-password-button": {
|
||||
"label": "Passwort ändern"
|
||||
},
|
||||
"set-permissions-button": {
|
||||
"label": "Berechtigungen ändern"
|
||||
},
|
||||
"user-form": {
|
||||
"submit": "Speichern"
|
||||
},
|
||||
"add-user": {
|
||||
"title": "Benutzer erstellen",
|
||||
"subtitle": "Erstellen eines neuen Benutzers"
|
||||
},
|
||||
"single-user": {
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Unbekannter Benutzer Fehler",
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Aktionen",
|
||||
"information-label": "Informationen",
|
||||
"back-label": "Zurück"
|
||||
},
|
||||
"validation": {
|
||||
"mail-invalid": "Diese E-Mail ist ungültig",
|
||||
"name-invalid": "Dieser Name ist ungültig",
|
||||
"displayname-invalid": "Dieser Anzeigename ist ungültig"
|
||||
},
|
||||
"password": {
|
||||
"set-password-successful": "Das Passwort wurde erfolgreich gespeichert."
|
||||
},
|
||||
"help": {
|
||||
"usernameHelpText": "Einzigartiger Name des Benutzers",
|
||||
"displayNameHelpText": "Anzeigename des Benutzers",
|
||||
"mailHelpText": "E-Mail Adresse des Benutzers",
|
||||
"adminHelpText": "Ein Administrator kann Repositories, Gruppen und Benutzer erstellen, bearbeiten und löschen.",
|
||||
"activeHelpText": "Aktivierung oder Deaktivierung eines Benutzers"
|
||||
},
|
||||
"users": {
|
||||
"title": "Benutzer",
|
||||
"subtitle": "Verwaltung der Benutzer",
|
||||
"createButton": "Benutzer erstellen"
|
||||
},
|
||||
"singleUser": {
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Benutzer Fehler",
|
||||
"menu": {
|
||||
"navigationLabel": "Benutzer Navigation",
|
||||
"informationNavLink": "Informationen",
|
||||
"settingsNavLink": "Einstellungen",
|
||||
"generalNavLink": "Generell",
|
||||
"setPasswordNavLink": "Passwort",
|
||||
"setPermissionsNavLink": "Berechtigungen"
|
||||
}
|
||||
},
|
||||
"addUser": {
|
||||
"title": "Benutzer erstellen",
|
||||
"subtitle": "Erstellen eines neuen Benutzers"
|
||||
},
|
||||
"deleteUser": {
|
||||
"subtitle": "Benutzer löschen",
|
||||
"button": "Löschen",
|
||||
"confirmAlert": {
|
||||
"title": "Benutzer löschen",
|
||||
"message": "Soll der Benutzer wirklich gelöscht werden?",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
}
|
||||
},
|
||||
"singleUserPassword": {
|
||||
"button": "Passwort setzen",
|
||||
"setPasswordSuccessful": "Das Passwort wurde erfolgreich gespeichert."
|
||||
},
|
||||
"userForm": {
|
||||
"subtitle": "Benutzer bearbeiten",
|
||||
"button": "Speichern"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,10 @@
|
||||
"previous": "Previous"
|
||||
},
|
||||
"profile": {
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Actions",
|
||||
"navigationLabel": "Profile Navigation",
|
||||
"informationNavLink": "Information",
|
||||
"changePasswordNavLink": "Change password",
|
||||
"settingsNavLink": "Settings",
|
||||
"username": "Username",
|
||||
"displayName": "Display Name",
|
||||
"mail": "E-Mail",
|
||||
@@ -67,6 +69,6 @@
|
||||
"passwordInvalid": "Password has to be between 6 and 32 characters",
|
||||
"passwordConfirmFailed": "Passwords have to be identical",
|
||||
"submit": "Submit",
|
||||
"changedSuccessfully": "Password successfully changed"
|
||||
"changedSuccessfully": "Password changed successfully"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"navigation-title": "Navigation"
|
||||
},
|
||||
"global-config": {
|
||||
"navigationLabel": "Configuration Navigation",
|
||||
"globalConfigurationNavLink": "Global Configuration",
|
||||
"title": "Configuration",
|
||||
"navigation-label": "Global Configuration",
|
||||
"error-title": "Error",
|
||||
"error-subtitle": "Unknown Config Error"
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown Config Error"
|
||||
},
|
||||
"config-form": {
|
||||
"submit": "Submit",
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
"title": "Groups",
|
||||
"subtitle": "Create, read, update and delete groups"
|
||||
},
|
||||
"single-group": {
|
||||
"error-title": "Error",
|
||||
"error-subtitle": "Unknown group error",
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Actions",
|
||||
"information-label": "Information",
|
||||
"back-label": "Back"
|
||||
"singleGroup": {
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown group error",
|
||||
"menu": {
|
||||
"navigationLabel": "Group Navigation",
|
||||
"informationNavLink": "Information",
|
||||
"settingsNavLink": "Settings",
|
||||
"generalNavLink": "General",
|
||||
"setPermissionsNavLink": "Permissions"
|
||||
}
|
||||
},
|
||||
"add-group": {
|
||||
"title": "Create Group",
|
||||
@@ -44,27 +47,25 @@
|
||||
"loading": "Loading...",
|
||||
"no-options": "No suggestion available"
|
||||
},
|
||||
|
||||
"group-form": {
|
||||
"groupForm": {
|
||||
"subtitle": "Edit Group",
|
||||
"submit": "Submit",
|
||||
"name-error": "Group name is invalid",
|
||||
"description-error": "Description is invalid",
|
||||
"nameError": "Group name is invalid",
|
||||
"descriptionError": "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": {
|
||||
"label": "Delete",
|
||||
"confirm-alert": {
|
||||
"deleteGroup": {
|
||||
"subtitle": "Delete Group",
|
||||
"button": "Delete",
|
||||
"confirmAlert": {
|
||||
"title": "Delete Group",
|
||||
"message": "Do you really want to delete the group?",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
}
|
||||
},
|
||||
"set-permissions-button": {
|
||||
"label": "Set Permissions"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"form": {
|
||||
"submit-button": {
|
||||
"label": "Set Permissions"
|
||||
},
|
||||
"set-permissions-successful": "Permissions set successfully"
|
||||
"setPermissions": {
|
||||
"button": "Set permissions",
|
||||
"setPermissionsSuccessful": "Permissions set successfully"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,41 +11,55 @@
|
||||
"name-invalid": "The repository name is invalid",
|
||||
"contact-invalid": "Contact must be a valid mail address"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"repositoryRoot": {
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown repository error",
|
||||
"menu": {
|
||||
"navigationLabel": "Repository Navigation",
|
||||
"informationNavLink": "Information",
|
||||
"historyNavLink": "Commits",
|
||||
"sourcesNavLink": "Sources",
|
||||
"settingsNavLink": "Settings",
|
||||
"generalNavLink": "General",
|
||||
"permissionsNavLink": "Permissions"
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"title": "Repositories",
|
||||
"subtitle": "Overview of available repositories",
|
||||
"create-button": "Create Repository"
|
||||
},
|
||||
"repository-root": {
|
||||
"error-title": "Error",
|
||||
"error-subtitle": "Unknown repository error",
|
||||
"actions-label": "Actions",
|
||||
"back-label": "Back",
|
||||
"navigation-label": "Navigation",
|
||||
"history": "Commits",
|
||||
"information": "Information",
|
||||
"permissions": "Permissions",
|
||||
"sources": "Sources"
|
||||
"createButton": "Create Repository"
|
||||
},
|
||||
"create": {
|
||||
"title": "Create Repository",
|
||||
"subtitle": "Create a new repository"
|
||||
},
|
||||
"repository-form": {
|
||||
"submit": "Save"
|
||||
"changesets": {
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Could not fetch changesets",
|
||||
"branchSelectorLabel": "Branches",
|
||||
"changeset": {
|
||||
"description": "Description",
|
||||
"summary": "Changeset {{id}} was committed {{time}}",
|
||||
"diffNotSupported": "Diff of changesets is not supported by the type of repository",
|
||||
"id": "ID",
|
||||
"contact": "Contact",
|
||||
"date": "Date"
|
||||
},
|
||||
"edit-nav-link": {
|
||||
"label": "Edit"
|
||||
},
|
||||
"delete-nav-action": {
|
||||
"label": "Delete",
|
||||
"confirm-alert": {
|
||||
"title": "Delete Repository",
|
||||
"message": "Do you really want to delete the repository?",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
"author": {
|
||||
"name": "Author",
|
||||
"mail": "Mail"
|
||||
}
|
||||
},
|
||||
"repositoryForm": {
|
||||
"subtitle": "Edit Repository",
|
||||
"submit": "Save"
|
||||
},
|
||||
"sources": {
|
||||
"file-tree": {
|
||||
"name": "Name",
|
||||
@@ -65,27 +79,6 @@
|
||||
"size": "Size"
|
||||
}
|
||||
},
|
||||
"changesets": {
|
||||
"diff": {
|
||||
"not-supported": "Diff of changesets is not supported by the type of repository"
|
||||
},
|
||||
"error-title": "Error",
|
||||
"error-subtitle": "Could not fetch changesets",
|
||||
"changeset": {
|
||||
"id": "ID",
|
||||
"description": "Description",
|
||||
"contact": "Contact",
|
||||
"date": "Date",
|
||||
"summary": "Changeset {{id}} was committed {{time}}"
|
||||
},
|
||||
"author": {
|
||||
"name": "Author",
|
||||
"mail": "Mail"
|
||||
}
|
||||
},
|
||||
"branch-selector": {
|
||||
"label": "Branches"
|
||||
},
|
||||
"permission": {
|
||||
"user": "User",
|
||||
"group": "Group",
|
||||
@@ -106,7 +99,7 @@
|
||||
"delete-permission-button": {
|
||||
"label": "Delete",
|
||||
"confirm-alert": {
|
||||
"title": "Delete Permission",
|
||||
"title": "Delete permission",
|
||||
"message": "Do you really want to delete the permission?",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
@@ -138,10 +131,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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."
|
||||
"deleteRepo": {
|
||||
"subtitle": "Delete Repository",
|
||||
"button": "Delete",
|
||||
"confirmAlert": {
|
||||
"title": "Delete repository",
|
||||
"message": "Do you really want to delete the repository?",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,59 +10,55 @@
|
||||
"creationDate": "Creation Date",
|
||||
"lastModified": "Last Modified"
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"subtitle": "Create, read, update and delete users"
|
||||
},
|
||||
"create-user-button": {
|
||||
"label": "Create User"
|
||||
},
|
||||
"delete-user-button": {
|
||||
"label": "Delete",
|
||||
"confirm-alert": {
|
||||
"title": "Delete User",
|
||||
"message": "Do you really want to delete the user?",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
}
|
||||
},
|
||||
"edit-user-button": {
|
||||
"label": "Edit"
|
||||
},
|
||||
"set-password-button": {
|
||||
"label": "Set Password"
|
||||
},
|
||||
"set-permissions-button": {
|
||||
"label": "Set Permissions"
|
||||
},
|
||||
"user-form": {
|
||||
"submit": "Submit"
|
||||
},
|
||||
"add-user": {
|
||||
"title": "Create User",
|
||||
"subtitle": "Create a new user"
|
||||
},
|
||||
"single-user": {
|
||||
"error-title": "Error",
|
||||
"error-subtitle": "Unknown user error",
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Actions",
|
||||
"information-label": "Information",
|
||||
"back-label": "Back"
|
||||
},
|
||||
"validation": {
|
||||
"mail-invalid": "This email is invalid",
|
||||
"name-invalid": "This name is invalid",
|
||||
"displayname-invalid": "This displayname is invalid"
|
||||
},
|
||||
"password": {
|
||||
"set-password-successful": "Password successfully set"
|
||||
},
|
||||
"help": {
|
||||
"usernameHelpText": "Unique name of the user.",
|
||||
"displayNameHelpText": "Display name of the user.",
|
||||
"mailHelpText": "Email address of the user.",
|
||||
"adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.",
|
||||
"activeHelpText": "Activate or deactivate the user."
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"subtitle": "Create, read, update and delete users",
|
||||
"createButton": "Create User"
|
||||
},
|
||||
"singleUser": {
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown user error",
|
||||
"menu": {
|
||||
"navigationLabel": "User Navigation",
|
||||
"informationNavLink": "Information",
|
||||
"settingsNavLink": "Settings",
|
||||
"generalNavLink": "General",
|
||||
"setPasswordNavLink": "Password",
|
||||
"setPermissionsNavLink": "Permissions"
|
||||
}
|
||||
},
|
||||
"addUser": {
|
||||
"title": "Create User",
|
||||
"subtitle": "Create a new user"
|
||||
},
|
||||
"deleteUser": {
|
||||
"subtitle": "Delete User",
|
||||
"button": "Delete",
|
||||
"confirmAlert": {
|
||||
"title": "Delete user",
|
||||
"message": "Do you really want to delete the user?",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
}
|
||||
},
|
||||
"singleUserPassword": {
|
||||
"button": "Set password",
|
||||
"setPasswordSuccessful": "Password successfully set"
|
||||
},
|
||||
"userForm": {
|
||||
"subtitle": "Edit User",
|
||||
"button": "Submit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import type { Links } from "@scm-manager/ui-types";
|
||||
import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components";
|
||||
import GlobalConfig from "./GlobalConfig";
|
||||
import type { History } from "history";
|
||||
import {connect} from "react-redux";
|
||||
import {compose} from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import { compose } from "redux";
|
||||
import { getLinks } from "../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
@@ -47,19 +47,21 @@ class Config extends React.Component<Props> {
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Route path={url} exact component={GlobalConfig} />
|
||||
<ExtensionPoint name="config.route"
|
||||
<ExtensionPoint
|
||||
name="config.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-one-quarter">
|
||||
<Navigation>
|
||||
<Section label={t("config.navigation-title")}>
|
||||
<Section label={t("config.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}`}
|
||||
label={t("global-config.navigation-label")}
|
||||
label={t("config.globalConfigurationNavLink")}
|
||||
/>
|
||||
<ExtensionPoint name="config.navigation"
|
||||
<ExtensionPoint
|
||||
name="config.navigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
@@ -83,4 +85,3 @@ export default compose(
|
||||
connect(mapStateToProps),
|
||||
translate("config")
|
||||
)(Config);
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ class GlobalConfig extends React.Component<Props, State> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("global-config.error-title")}
|
||||
subtitle={t("global-config.error-subtitle")}
|
||||
title={t("config.errorTitle")}
|
||||
subtitle={t("config.errorSubtitle")}
|
||||
error={error}
|
||||
configUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
@@ -91,7 +91,7 @@ class GlobalConfig extends React.Component<Props, State> {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title title={t("global-config.title")} />
|
||||
<Title title={t("config.title")} />
|
||||
{this.renderConfigChangedNotification()}
|
||||
<ConfigForm
|
||||
submitForm={config => this.modifyConfig(config)}
|
||||
|
||||
@@ -12,11 +12,13 @@ import {
|
||||
ErrorPage,
|
||||
Page,
|
||||
Navigation,
|
||||
SubNavigation,
|
||||
Section,
|
||||
NavLink
|
||||
} from "@scm-manager/ui-components";
|
||||
import ChangeUserPassword from "./ChangeUserPassword";
|
||||
import ProfileInfo from "./ProfileInfo";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
me: Me,
|
||||
@@ -57,26 +59,43 @@ class Profile extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
const extensionProps = {
|
||||
me,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title={me.displayName}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Route path={url} exact render={() => <ProfileInfo me={me} />} />
|
||||
<Route
|
||||
path={`${url}/password`}
|
||||
path={`${url}/settings/password`}
|
||||
render={() => <ChangeUserPassword me={me} />}
|
||||
/>
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("profile.navigation-label")}>
|
||||
<NavLink to={`${url}`} icon="fas fa-info-circle" label={t("profile.information")} />
|
||||
</Section>
|
||||
<Section label={t("profile.actions-label")}>
|
||||
<Section label={t("profile.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}/password`}
|
||||
label={t("profile.change-password")}
|
||||
to={`${url}`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("profile.informationNavLink")}
|
||||
/>
|
||||
<SubNavigation
|
||||
to={`${url}/settings/password`}
|
||||
label={t("profile.settingsNavLink")}
|
||||
>
|
||||
<NavLink
|
||||
to={`${url}/settings/password`}
|
||||
label={t("profile.changePasswordNavLink")}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="profile.subnavigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
|
||||
@@ -42,16 +42,6 @@ class ProfileInfo extends React.Component<Props, State> {
|
||||
<MailLink address={me.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.groups")}</td>
|
||||
<td className="content">
|
||||
<ul>
|
||||
{me.groups.map((group) => {
|
||||
return <li>{group}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
Subtitle,
|
||||
AutocompleteAddEntryToTableField,
|
||||
LabelWithHelpIcon,
|
||||
MemberNameTable,
|
||||
@@ -71,36 +72,43 @@ class GroupForm extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, loading } = this.props;
|
||||
const { loading, t } = this.props;
|
||||
const { group } = this.state;
|
||||
let nameField = null;
|
||||
let subtitle = null;
|
||||
if (!this.props.group) {
|
||||
// create new group
|
||||
nameField = (
|
||||
<InputField
|
||||
label={t("group.name")}
|
||||
errorMessage={t("group-form.name-error")}
|
||||
errorMessage={t("groupForm.nameError")}
|
||||
onChange={this.handleGroupNameChange}
|
||||
value={group.name}
|
||||
validationError={this.state.nameValidationError}
|
||||
helpText={t("group-form.help.nameHelpText")}
|
||||
helpText={t("groupForm.help.nameHelpText")}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// edit existing group
|
||||
subtitle = <Subtitle subtitle={t("groupForm.subtitle")} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{subtitle}
|
||||
<form onSubmit={this.submit}>
|
||||
{nameField}
|
||||
<Textarea
|
||||
label={t("group.description")}
|
||||
errorMessage={t("group-form.description-error")}
|
||||
errorMessage={t("groupForm.descriptionError")}
|
||||
onChange={this.handleDescriptionChange}
|
||||
value={group.description}
|
||||
validationError={false}
|
||||
helpText={t("group-form.help.descriptionHelpText")}
|
||||
helpText={t("groupForm.help.descriptionHelpText")}
|
||||
/>
|
||||
<LabelWithHelpIcon
|
||||
label={t("group.members")}
|
||||
helpText={t("group-form.help.memberHelpText")}
|
||||
helpText={t("groupForm.help.memberHelpText")}
|
||||
/>
|
||||
<MemberNameTable
|
||||
members={group.members}
|
||||
@@ -120,10 +128,11 @@ class GroupForm extends React.Component<Props, State> {
|
||||
/>
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
label={t("group-form.submit")}
|
||||
label={t("groupForm.submit")}
|
||||
loading={loading}
|
||||
/>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { NavAction, confirmAlert } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
confirmDialog?: boolean,
|
||||
t: string => string,
|
||||
deleteGroup: (group: Group) => void
|
||||
};
|
||||
|
||||
export class DeleteGroupNavLink extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
};
|
||||
|
||||
deleteGroup = () => {
|
||||
this.props.deleteGroup(this.props.group);
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("delete-group-button.confirm-alert.title"),
|
||||
message: t("delete-group-button.confirm-alert.message"),
|
||||
buttons: [
|
||||
{
|
||||
label: t("delete-group-button.confirm-alert.submit"),
|
||||
onClick: () => this.deleteGroup(),
|
||||
},
|
||||
{
|
||||
label: t("delete-group-button.confirm-alert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
isDeletable = () => {
|
||||
return this.props.group._links.delete;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { confirmDialog, t } = this.props;
|
||||
const action = confirmDialog ? this.confirmDelete : this.deleteGroup;
|
||||
|
||||
if (!this.isDeletable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavAction icon="fas fa-times" label={t("delete-group-button.label")} action={action} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(DeleteGroupNavLink);
|
||||
@@ -1,82 +0,0 @@
|
||||
import React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import "../../../tests/enzyme";
|
||||
import "../../../tests/i18n";
|
||||
import DeleteGroupNavLink from "./DeleteGroupNavLink";
|
||||
|
||||
import { confirmAlert } from "@scm-manager/ui-components";
|
||||
jest.mock("@scm-manager/ui-components", () => ({
|
||||
confirmAlert: jest.fn(),
|
||||
NavAction: require.requireActual("@scm-manager/ui-components").NavAction
|
||||
}));
|
||||
|
||||
describe("DeleteGroupNavLink", () => {
|
||||
it("should render nothing, if the delete link is missing", () => {
|
||||
const group = {
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<DeleteGroupNavLink group={group} deleteGroup={() => {}} />
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const group = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/groups"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteGroupNavLink group={group} deleteGroup={() => {}} />
|
||||
);
|
||||
expect(navLink.text()).not.toBe("");
|
||||
});
|
||||
|
||||
it("should open the confirm dialog on navLink click", () => {
|
||||
const group = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/groups"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteGroupNavLink group={group} deleteGroup={() => {}} />
|
||||
);
|
||||
navLink.find("a").simulate("click");
|
||||
|
||||
expect(confirmAlert.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should call the delete group function with delete url", () => {
|
||||
const group = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/groups"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let calledUrl = null;
|
||||
function capture(group) {
|
||||
calledUrl = group._links.delete.href;
|
||||
}
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteGroupNavLink
|
||||
group={group}
|
||||
confirmDialog={false}
|
||||
deleteGroup={capture}
|
||||
/>
|
||||
);
|
||||
navLink.find("a").simulate("click");
|
||||
|
||||
expect(calledUrl).toBe("/groups");
|
||||
});
|
||||
});
|
||||
@@ -1,29 +1,28 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
group: Group,
|
||||
editUrl: string,
|
||||
group: Group
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = {};
|
||||
|
||||
class EditGroupNavLink extends React.Component<Props, State> {
|
||||
render() {
|
||||
const { t, editUrl } = this.props;
|
||||
if (!this.isEditable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={editUrl} icon="fas fa-cog" label={t("edit-group-button.label")} />;
|
||||
}
|
||||
|
||||
class EditGroupNavLink extends React.Component<Props> {
|
||||
isEditable = () => {
|
||||
return this.props.group._links.update;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, editUrl } = this.props;
|
||||
|
||||
if (!this.isEditable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={editUrl} label={t("singleGroup.menu.generalNavLink")} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("groups")(EditGroupNavLink);
|
||||
|
||||
@@ -17,7 +17,7 @@ class ChangePermissionNavLink extends React.Component<Props> {
|
||||
if (!this.hasPermissionToSetPermission()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={permissionsUrl} label={t("set-permissions-button.label")} />;
|
||||
return <NavLink to={permissionsUrl} label={t("singleGroup.menu.setPermissionsNavLink")} />;
|
||||
}
|
||||
|
||||
hasPermissionToSetPermission = () => {
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default as DeleteGroupNavLink } from "./DeleteGroupNavLink";
|
||||
export { default as EditGroupNavLink } from "./EditGroupNavLink";
|
||||
export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink";
|
||||
|
||||
113
scm-ui/src/groups/containers/DeleteGroup.js
Normal file
113
scm-ui/src/groups/containers/DeleteGroup.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
DeleteButton,
|
||||
confirmAlert,
|
||||
ErrorNotification
|
||||
} from "@scm-manager/ui-components";
|
||||
import {
|
||||
deleteGroup,
|
||||
getDeleteGroupFailure,
|
||||
isDeleteGroupPending
|
||||
} from "../modules/groups";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
group: Group,
|
||||
confirmDialog?: boolean,
|
||||
deleteGroup: (group: Group, callback?: () => void) => void,
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
export class DeleteGroup extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
};
|
||||
|
||||
deleteGroup = () => {
|
||||
this.props.deleteGroup(this.props.group, this.groupDeleted);
|
||||
};
|
||||
|
||||
groupDeleted = () => {
|
||||
this.props.history.push("/groups");
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("deleteGroup.confirmAlert.title"),
|
||||
message: t("deleteGroup.confirmAlert.message"),
|
||||
buttons: [
|
||||
{
|
||||
label: t("deleteGroup.confirmAlert.submit"),
|
||||
onClick: () => this.deleteGroup()
|
||||
},
|
||||
{
|
||||
label: t("deleteGroup.confirmAlert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
isDeletable = () => {
|
||||
return this.props.group._links.delete;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, error, confirmDialog, t } = this.props;
|
||||
const action = confirmDialog ? this.confirmDelete : this.deleteGroup;
|
||||
|
||||
if (!this.isDeletable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("deleteGroup.subtitle")} />
|
||||
<ErrorNotification error={error} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<DeleteButton
|
||||
label={t("deleteGroup.button")}
|
||||
action={action}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loading = isDeleteGroupPending(state, ownProps.group.name);
|
||||
const error = getDeleteGroupFailure(state, ownProps.group.name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
deleteGroup: (group: Group, callback?: () => void) => {
|
||||
dispatch(deleteGroup(group, callback));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(translate("groups")(DeleteGroup)));
|
||||
@@ -3,9 +3,9 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
import {
|
||||
modifyGroup,
|
||||
getModifyGroupFailure,
|
||||
isModifyGroupPending,
|
||||
modifyGroup,
|
||||
modifyGroupReset
|
||||
} from "../modules/groups";
|
||||
import type { History } from "history";
|
||||
@@ -13,12 +13,13 @@ import { withRouter } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
fetchGroup: (name: string) => void,
|
||||
modifyGroup: (group: Group, callback?: () => void) => void,
|
||||
modifyGroupReset: Group => void,
|
||||
fetchGroup: (name: string) => void,
|
||||
autocompleteLink: string,
|
||||
history: History,
|
||||
loading?: boolean,
|
||||
@@ -54,7 +55,7 @@ class EditGroup extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { group, loading, error } = this.props;
|
||||
const { loading, error, group } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ErrorNotification error={error} />
|
||||
@@ -66,6 +67,8 @@ class EditGroup extends React.Component<Props> {
|
||||
loading={loading}
|
||||
loadUserSuggestions={this.loadUserAutocompletion}
|
||||
/>
|
||||
<hr />
|
||||
<DeleteGroup group={group} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,33 +6,30 @@ import {
|
||||
ErrorPage,
|
||||
Loading,
|
||||
Navigation,
|
||||
SubNavigation,
|
||||
Section,
|
||||
NavLink
|
||||
} from "@scm-manager/ui-components";
|
||||
import { Route } from "react-router";
|
||||
import { Details } from "./../components/table";
|
||||
import {
|
||||
DeleteGroupNavLink,
|
||||
EditGroupNavLink,
|
||||
SetPermissionsNavLink
|
||||
} from "./../components/navLinks";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import type { History } from "history";
|
||||
import {
|
||||
deleteGroup,
|
||||
fetchGroupByName,
|
||||
getGroupByName,
|
||||
isFetchGroupPending,
|
||||
getFetchGroupFailure,
|
||||
getDeleteGroupFailure,
|
||||
isDeleteGroupPending
|
||||
getFetchGroupFailure
|
||||
} from "../modules/groups";
|
||||
|
||||
import { translate } from "react-i18next";
|
||||
import EditGroup from "./EditGroup";
|
||||
import { getGroupsLink } from "../../modules/indexResource";
|
||||
import SetPermissions from "../../permissions/components/SetPermissions";
|
||||
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
@@ -42,7 +39,6 @@ type Props = {
|
||||
groupLink: string,
|
||||
|
||||
// dispatcher functions
|
||||
deleteGroup: (group: Group, callback?: () => void) => void,
|
||||
fetchGroupByName: (string, string) => void,
|
||||
|
||||
// context objects
|
||||
@@ -63,14 +59,6 @@ class SingleGroup extends React.Component<Props> {
|
||||
return url;
|
||||
};
|
||||
|
||||
deleteGroup = (group: Group) => {
|
||||
this.props.deleteGroup(group, this.groupDeleted);
|
||||
};
|
||||
|
||||
groupDeleted = () => {
|
||||
this.props.history.push("/groups");
|
||||
};
|
||||
|
||||
matchedUrl = () => {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
@@ -81,8 +69,8 @@ class SingleGroup extends React.Component<Props> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("single-group.error-title")}
|
||||
subtitle={t("single-group.error-subtitle")}
|
||||
title={t("singleGroup.errorTitle")}
|
||||
subtitle={t("singleGroup.errorSubtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
@@ -109,15 +97,17 @@ class SingleGroup extends React.Component<Props> {
|
||||
component={() => <Details group={group} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/edit`}
|
||||
path={`${url}/settings/general`}
|
||||
exact
|
||||
component={() => <EditGroup group={group} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/permissions`}
|
||||
path={`${url}/settings/permissions`}
|
||||
exact
|
||||
component={() => (
|
||||
<SetPermissions selectedPermissionsLink={group._links.permissions} />
|
||||
<SetPermissions
|
||||
selectedPermissionsLink={group._links.permissions}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
@@ -128,33 +118,35 @@ class SingleGroup extends React.Component<Props> {
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("single-group.navigation-label")}>
|
||||
<Section label={t("singleGroup.menu.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("single-group.information-label")}
|
||||
/>
|
||||
<SetPermissionsNavLink
|
||||
group={group}
|
||||
permissionsUrl={`${url}/permissions`}
|
||||
label={t("singleGroup.menu.informationNavLink")}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="group.navigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</Section>
|
||||
<Section label={t("single-group.actions-label")}>
|
||||
<DeleteGroupNavLink
|
||||
<SubNavigation
|
||||
to={`${url}/settings/general`}
|
||||
label={t("singleGroup.menu.settingsNavLink")}
|
||||
>
|
||||
<EditGroupNavLink
|
||||
group={group}
|
||||
deleteGroup={this.deleteGroup}
|
||||
editUrl={`${url}/settings/general`}
|
||||
/>
|
||||
<EditGroupNavLink group={group} editUrl={`${url}/edit`} />
|
||||
<NavLink
|
||||
to="/groups"
|
||||
icon="fas fa-undo-alt"
|
||||
label={t("single-group.back-label")}
|
||||
<SetPermissionsNavLink
|
||||
group={group}
|
||||
permissionsUrl={`${url}/settings/permissions`}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="group.subnavigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
@@ -167,10 +159,8 @@ class SingleGroup extends React.Component<Props> {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const name = ownProps.match.params.name;
|
||||
const group = getGroupByName(state, name);
|
||||
const loading =
|
||||
isFetchGroupPending(state, name) || isDeleteGroupPending(state, name);
|
||||
const error =
|
||||
getFetchGroupFailure(state, name) || getDeleteGroupFailure(state, name);
|
||||
const loading = isFetchGroupPending(state, name);
|
||||
const error = getFetchGroupFailure(state, name);
|
||||
const groupLink = getGroupsLink(state);
|
||||
|
||||
return {
|
||||
@@ -186,9 +176,6 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchGroupByName: (link: string, name: string) => {
|
||||
dispatch(fetchGroupByName(link, name));
|
||||
},
|
||||
deleteGroup: (group: Group, callback?: () => void) => {
|
||||
dispatch(deleteGroup(group, callback));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ class SetPermissions extends React.Component<Props, State> {
|
||||
message = (
|
||||
<Notification
|
||||
type={"success"}
|
||||
children={t("form.set-permissions-successful")}
|
||||
children={t("setPermissions.setPermissionsSuccessful")}
|
||||
onClose={() => this.onClose()}
|
||||
/>
|
||||
);
|
||||
@@ -128,7 +128,7 @@ class SetPermissions extends React.Component<Props, State> {
|
||||
<SubmitButton
|
||||
disabled={!this.state.permissionsChanged}
|
||||
loading={loading}
|
||||
label={t("form.submit-button.label")}
|
||||
label={t("setPermissions.button")}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { NavAction, confirmAlert } from "@scm-manager/ui-components";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
confirmDialog?: boolean,
|
||||
delete: Repository => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class DeleteNavAction extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
};
|
||||
|
||||
delete = () => {
|
||||
this.props.delete(this.props.repository);
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("delete-nav-action.confirm-alert.title"),
|
||||
message: t("delete-nav-action.confirm-alert.message"),
|
||||
buttons: [
|
||||
{
|
||||
label: t("delete-nav-action.confirm-alert.submit"),
|
||||
onClick: () => this.delete()
|
||||
},
|
||||
{
|
||||
label: t("delete-nav-action.confirm-alert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
isDeletable = () => {
|
||||
return this.props.repository._links.delete;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { confirmDialog, t } = this.props;
|
||||
const action = confirmDialog ? this.confirmDelete : this.delete();
|
||||
|
||||
if (!this.isDeletable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavAction action={action} icon="fas fa-times" label={t("delete-nav-action.label")} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(DeleteNavAction);
|
||||
@@ -1,82 +0,0 @@
|
||||
import React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import "../../tests/enzyme";
|
||||
import "../../tests/i18n";
|
||||
import DeleteNavAction from "./DeleteNavAction";
|
||||
|
||||
import { confirmAlert } from "@scm-manager/ui-components";
|
||||
jest.mock("@scm-manager/ui-components", () => ({
|
||||
confirmAlert: jest.fn(),
|
||||
NavAction: require.requireActual("@scm-manager/ui-components").NavAction
|
||||
}));
|
||||
|
||||
describe("DeleteNavAction", () => {
|
||||
it("should render nothing, if the delete link is missing", () => {
|
||||
const repository = {
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<DeleteNavAction repository={repository} delete={() => {}} />
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/repositories"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteNavAction repository={repository} delete={() => {}} />
|
||||
);
|
||||
expect(navLink.text()).not.toBe("");
|
||||
});
|
||||
|
||||
it("should open the confirm dialog on navLink click", () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/repositorys"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteNavAction repository={repository} delete={() => {}} />
|
||||
);
|
||||
navLink.find("a").simulate("click");
|
||||
|
||||
expect(confirmAlert.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should call the delete repository function with delete url", () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/repos"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let calledUrl = null;
|
||||
function capture(repository) {
|
||||
calledUrl = repository._links.delete.href;
|
||||
}
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteNavAction
|
||||
repository={repository}
|
||||
confirmDialog={false}
|
||||
delete={capture}
|
||||
/>
|
||||
);
|
||||
navLink.find("a").simulate("click");
|
||||
|
||||
expect(calledUrl).toBe("/repos");
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,28 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = { editUrl: string, t: string => string, repository: Repository };
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
editUrl: string,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class EditNavLink extends React.Component<Props> {
|
||||
class EditRepoNavLink extends React.Component<Props> {
|
||||
isEditable = () => {
|
||||
return this.props.repository._links.update;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { editUrl, t } = this.props;
|
||||
|
||||
if (!this.isEditable()) {
|
||||
return null;
|
||||
}
|
||||
const { editUrl, t } = this.props;
|
||||
return <NavLink to={editUrl} icon="fas fa-cog" label={t("edit-nav-link.label")} />;
|
||||
return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(EditNavLink);
|
||||
export default translate("repos")(EditRepoNavLink);
|
||||
@@ -3,9 +3,9 @@ import { shallow, mount } from "enzyme";
|
||||
import "../../tests/enzyme";
|
||||
import "../../tests/i18n";
|
||||
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
||||
import EditNavLink from "./EditNavLink";
|
||||
import EditRepoNavLink from "./EditRepoNavLink";
|
||||
|
||||
describe("EditNavLink", () => {
|
||||
describe("GeneralNavLink", () => {
|
||||
const options = new ReactRouterEnzymeContext();
|
||||
|
||||
it("should render nothing, if the modify link is missing", () => {
|
||||
@@ -14,7 +14,7 @@ describe("EditNavLink", () => {
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<EditNavLink repository={repository} editUrl="" />,
|
||||
<EditRepoNavLink repository={repository} editUrl="" />,
|
||||
options.get()
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
@@ -30,9 +30,9 @@ describe("EditNavLink", () => {
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<EditNavLink repository={repository} editUrl="" />,
|
||||
<EditRepoNavLink repository={repository} editUrl="" />,
|
||||
options.get()
|
||||
);
|
||||
expect(navLink.text()).toBe(" edit-nav-link.label");
|
||||
expect(navLink.text()).toBe("repositoryRoot.menu.generalNavLink");
|
||||
});
|
||||
});
|
||||
@@ -20,7 +20,7 @@ class PermissionsNavLink extends React.Component<Props> {
|
||||
}
|
||||
const { permissionUrl, t } = this.props;
|
||||
return (
|
||||
<NavLink to={permissionUrl} icon="fas fa-lock" label={t("repository-root.permissions")} />
|
||||
<NavLink to={permissionUrl} label={t("repositoryRoot.menu.permissionsNavLink")} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,6 @@ describe("PermissionsNavLink", () => {
|
||||
<PermissionsNavLink repository={repository} permissionUrl="" />,
|
||||
options.get()
|
||||
);
|
||||
expect(navLink.text()).toBe(" repository-root.permissions");
|
||||
expect(navLink.text()).toBe("repositoryRoot.menu.permissionsNavLink");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
Subtitle,
|
||||
InputField,
|
||||
Select,
|
||||
SubmitButton,
|
||||
@@ -81,7 +82,15 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
const { loading, t } = this.props;
|
||||
const repository = this.state.repository;
|
||||
|
||||
let subtitle = null;
|
||||
if (this.props.repository) {
|
||||
// edit existing repo
|
||||
subtitle = <Subtitle subtitle={t("repositoryForm.subtitle")} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{subtitle}
|
||||
<form onSubmit={this.submit}>
|
||||
{this.renderCreateOnlyFields()}
|
||||
<InputField
|
||||
@@ -102,9 +111,10 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("repository-form.submit")}
|
||||
label={t("repositoryForm.submit")}
|
||||
/>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class RepositoryEntry extends React.Component<Props> {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-cog fa-lg"
|
||||
to={repositoryLink + "/edit"}
|
||||
to={repositoryLink + "/settings/general"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ class ChangesetView extends React.Component<Props> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("changeset-error.title")}
|
||||
subtitle={t("changeset-error.subtitle")}
|
||||
title={t("changesets.errorTitle")}
|
||||
subtitle={t("changesets.errorSubtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -101,7 +101,7 @@ class BranchRoot extends React.Component<Props> {
|
||||
if (repository._links.branches) {
|
||||
return (
|
||||
<BranchSelector
|
||||
label={t("branch-selector.label")}
|
||||
label={t("changesets.branchSelectorLabel")}
|
||||
branches={branches}
|
||||
selectedBranch={selected}
|
||||
selected={(b: Branch) => {
|
||||
|
||||
114
scm-ui/src/repos/containers/DeleteRepo.js
Normal file
114
scm-ui/src/repos/containers/DeleteRepo.js
Normal file
@@ -0,0 +1,114 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
DeleteButton,
|
||||
confirmAlert,
|
||||
ErrorNotification
|
||||
} from "@scm-manager/ui-components";
|
||||
import {
|
||||
deleteRepo,
|
||||
getDeleteRepoFailure,
|
||||
isDeleteRepoPending
|
||||
} from "../modules/repos";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
repository: Repository,
|
||||
confirmDialog?: boolean,
|
||||
deleteRepo: (Repository, () => void) => void,
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class DeleteRepo extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
};
|
||||
|
||||
deleted = () => {
|
||||
this.props.history.push("/repos");
|
||||
};
|
||||
|
||||
deleteRepo = () => {
|
||||
this.props.deleteRepo(this.props.repository, this.deleted);
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("deleteRepo.confirmAlert.title"),
|
||||
message: t("deleteRepo.confirmAlert.message"),
|
||||
buttons: [
|
||||
{
|
||||
label: t("deleteRepo.confirmAlert.submit"),
|
||||
onClick: () => this.deleteRepo()
|
||||
},
|
||||
{
|
||||
label: t("deleteRepo.confirmAlert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
isDeletable = () => {
|
||||
return this.props.repository._links.delete;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, error, confirmDialog, t } = this.props;
|
||||
const action = confirmDialog ? this.confirmDelete : this.deleteRepo;
|
||||
|
||||
if (!this.isDeletable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("deleteRepo.subtitle")} />
|
||||
<ErrorNotification error={error} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<DeleteButton
|
||||
label={t("deleteRepo.button")}
|
||||
action={action}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { namespace, name } = ownProps.repository;
|
||||
const loading = isDeleteRepoPending(state, namespace, name);
|
||||
const error = getDeleteRepoFailure(state, namespace, name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
deleteRepo: (repo: Repository, callback: () => void) => {
|
||||
dispatch(deleteRepo(repo, callback));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(translate("repos")(DeleteRepo)));
|
||||
@@ -1,8 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import RepositoryForm from "../components/form";
|
||||
import DeleteRepo from "./DeleteRepo";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import {
|
||||
modifyRepo,
|
||||
@@ -10,34 +11,55 @@ import {
|
||||
getModifyRepoFailure,
|
||||
modifyRepoReset
|
||||
} from "../modules/repos";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
modifyRepo: (Repository, () => void) => void,
|
||||
modifyRepoReset: Repository => void,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
|
||||
modifyRepo: (Repository, () => void) => void,
|
||||
modifyRepoReset: Repository => void,
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
history: History
|
||||
repository: Repository,
|
||||
history: History,
|
||||
match: any
|
||||
};
|
||||
|
||||
class Edit extends React.Component<Props> {
|
||||
class EditRepo extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const { modifyRepoReset, repository } = this.props;
|
||||
modifyRepoReset(repository);
|
||||
}
|
||||
|
||||
repoModified = () => {
|
||||
const { history, repository } = this.props;
|
||||
history.push(`/repo/${repository.namespace}/${repository.name}`);
|
||||
};
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
return url.substring(0, url.length - 2);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
matchedUrl = () => {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, error } = this.props;
|
||||
const { loading, error, repository } = this.props;
|
||||
|
||||
const url = this.matchedUrl();
|
||||
|
||||
const extensionProps = {
|
||||
repository,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ErrorNotification error={error} />
|
||||
@@ -48,6 +70,13 @@ class Edit extends React.Component<Props> {
|
||||
this.props.modifyRepo(repo, this.repoModified);
|
||||
}}
|
||||
/>
|
||||
<hr />
|
||||
<ExtensionPoint
|
||||
name="repo-config.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
<DeleteRepo repository={repository} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -77,4 +106,4 @@ const mapDispatchToProps = dispatch => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(withRouter(Edit)));
|
||||
)(withRouter(EditRepo));
|
||||
@@ -90,7 +90,7 @@ class Overview extends React.Component<Props> {
|
||||
if (showCreateButton) {
|
||||
return (
|
||||
<CreateButton
|
||||
label={t("overview.create-button")}
|
||||
label={t("overview.createButton")}
|
||||
link="/repos/create"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {deleteRepo, fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos";
|
||||
import {
|
||||
fetchRepoByName,
|
||||
getFetchRepoFailure,
|
||||
getRepository,
|
||||
isFetchRepoPending
|
||||
} from "../modules/repos";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {Route, Switch} from "react-router-dom";
|
||||
import type {Repository} from "@scm-manager/ui-types";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components";
|
||||
import {translate} from "react-i18next";
|
||||
import {
|
||||
ErrorPage,
|
||||
Loading,
|
||||
Navigation,
|
||||
SubNavigation,
|
||||
NavLink,
|
||||
Page,
|
||||
Section
|
||||
} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import RepositoryDetails from "../components/RepositoryDetails";
|
||||
import DeleteNavAction from "../components/DeleteNavAction";
|
||||
import Edit from "../containers/Edit";
|
||||
import EditRepo from "./EditRepo";
|
||||
import Permissions from "../permissions/containers/Permissions";
|
||||
|
||||
import type {History} from "history";
|
||||
import EditNavLink from "../components/EditNavLink";
|
||||
import type { History } from "history";
|
||||
import EditRepoNavLink from "../components/EditRepoNavLink";
|
||||
|
||||
import BranchRoot from "./ChangesetsRoot";
|
||||
import ChangesetView from "./ChangesetView";
|
||||
@@ -35,7 +47,6 @@ type Props = {
|
||||
|
||||
// dispatch functions
|
||||
fetchRepoByName: (link: string, namespace: string, name: string) => void,
|
||||
deleteRepo: (repository: Repository, () => void) => void,
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
@@ -61,14 +72,6 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
return this.stripEndingSlash(this.props.match.url);
|
||||
};
|
||||
|
||||
deleted = () => {
|
||||
this.props.history.push("/repos");
|
||||
};
|
||||
|
||||
delete = (repository: Repository) => {
|
||||
this.props.deleteRepo(repository, this.deleted);
|
||||
};
|
||||
|
||||
matches = (route: any) => {
|
||||
const url = this.matchedUrl();
|
||||
const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`);
|
||||
@@ -81,8 +84,8 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("repository-root.error-title")}
|
||||
subtitle={t("repository-root.error-subtitle")}
|
||||
title={t("repositoryRoot.errorTitle")}
|
||||
subtitle={t("repositoryRoot.errorSubtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
@@ -111,11 +114,11 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
component={() => <RepositoryDetails repository={repository} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/edit`}
|
||||
component={() => <Edit repository={repository} />}
|
||||
path={`${url}/settings/general`}
|
||||
component={() => <EditRepo repository={repository} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/permissions`}
|
||||
path={`${url}/settings/permissions`}
|
||||
render={() => (
|
||||
<Permissions
|
||||
namespace={this.props.repository.namespace}
|
||||
@@ -170,14 +173,18 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("repository-root.navigation-label")}>
|
||||
<NavLink to={url} icon="fas fa-info-circle" label={t("repository-root.information")} />
|
||||
<Section label={t("repositoryRoot.menu.navigationLabel")}>
|
||||
<NavLink
|
||||
to={url}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("repositoryRoot.menu.informationNavLink")}
|
||||
/>
|
||||
<RepositoryNavLink
|
||||
repository={repository}
|
||||
linkName="changesets"
|
||||
to={`${url}/changesets/`}
|
||||
icon="fas fa-code-branch"
|
||||
label={t("repository-root.history")}
|
||||
label={t("repositoryRoot.menu.historyNavLink")}
|
||||
activeWhenMatch={this.matches}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
@@ -186,23 +193,32 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
linkName="sources"
|
||||
to={`${url}/sources`}
|
||||
icon="fas fa-code"
|
||||
label={t("repository-root.sources")}
|
||||
label={t("repositoryRoot.menu.sourcesNavLink")}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
<PermissionsNavLink
|
||||
permissionUrl={`${url}/permissions`}
|
||||
repository={repository}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="repository.navigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</Section>
|
||||
<Section label={t("repository-root.actions-label")}>
|
||||
<DeleteNavAction repository={repository} delete={this.delete} />
|
||||
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
||||
<NavLink to="/repos" icon="fas fa-undo" label={t("repository-root.back-label")} />
|
||||
<SubNavigation
|
||||
to={`${url}/settings/general`}
|
||||
label={t("repositoryRoot.menu.settingsNavLink")}
|
||||
>
|
||||
<EditRepoNavLink
|
||||
repository={repository}
|
||||
editUrl={`${url}/settings/general`}
|
||||
/>
|
||||
<PermissionsNavLink
|
||||
permissionUrl={`${url}/settings/permissions`}
|
||||
repository={repository}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="repository.subnavigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
@@ -234,9 +250,6 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchRepoByName: (link: string, namespace: string, name: string) => {
|
||||
dispatch(fetchRepoByName(link, namespace, name));
|
||||
},
|
||||
deleteRepo: (repository: Repository, callback: () => void) => {
|
||||
dispatch(deleteRepo(repository, callback));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ class Sources extends React.Component<Props> {
|
||||
<BranchSelector
|
||||
branches={branches}
|
||||
selectedBranch={revision}
|
||||
label={t("branch-selector.label")}
|
||||
label={t("changesets.branchSelectorLabel")}
|
||||
selected={(b: Branch) => {
|
||||
this.branchSelected(b);
|
||||
}}
|
||||
|
||||
@@ -90,7 +90,7 @@ class SetUserPassword extends React.Component<Props, State> {
|
||||
message = (
|
||||
<Notification
|
||||
type={"success"}
|
||||
children={t("password.set-password-successful")}
|
||||
children={t("singleUserPassword.setPasswordSuccessful")}
|
||||
onClose={() => this.onClose()}
|
||||
/>
|
||||
);
|
||||
@@ -108,7 +108,7 @@ class SetUserPassword extends React.Component<Props, State> {
|
||||
<SubmitButton
|
||||
disabled={!this.state.passwordValid}
|
||||
loading={loading}
|
||||
label={t("user-form.submit")}
|
||||
label={t("singleUserPassword.button")}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
Checkbox,
|
||||
InputField,
|
||||
PasswordConfirmation,
|
||||
@@ -113,7 +114,9 @@ class UserForm extends React.Component<Props, State> {
|
||||
|
||||
let nameField = null;
|
||||
let passwordChangeField = null;
|
||||
let subtitle = null;
|
||||
if (!this.props.user) {
|
||||
// create new user
|
||||
nameField = (
|
||||
<div className="column is-half">
|
||||
<InputField
|
||||
@@ -130,8 +133,13 @@ class UserForm extends React.Component<Props, State> {
|
||||
passwordChangeField = (
|
||||
<PasswordConfirmation passwordChanged={this.handlePasswordChange} />
|
||||
);
|
||||
} else {
|
||||
// edit existing user
|
||||
subtitle = <Subtitle subtitle={t("userForm.subtitle")} />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{subtitle}
|
||||
<form onSubmit={this.submit}>
|
||||
<div className="columns is-multiline">
|
||||
{nameField}
|
||||
@@ -178,11 +186,12 @@ class UserForm extends React.Component<Props, State> {
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("user-form.submit")}
|
||||
label={t("userForm.button")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { CreateButton } from "@scm-manager/ui-components";
|
||||
|
||||
// TODO remove
|
||||
type Props = {
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class CreateUserButton extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<CreateButton label={t("create-user-button.label")} link="/users/add" />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("users")(CreateUserButton);
|
||||
@@ -1,56 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import { NavAction, confirmAlert } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
user: User,
|
||||
confirmDialog?: boolean,
|
||||
t: string => string,
|
||||
deleteUser: (user: User) => void
|
||||
};
|
||||
|
||||
class DeleteUserNavLink extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
};
|
||||
|
||||
deleteUser = () => {
|
||||
this.props.deleteUser(this.props.user);
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("delete-user-button.confirm-alert.title"),
|
||||
message: t("delete-user-button.confirm-alert.message"),
|
||||
buttons: [
|
||||
{
|
||||
label: t("delete-user-button.confirm-alert.submit"),
|
||||
onClick: () => this.deleteUser()
|
||||
},
|
||||
{
|
||||
label: t("delete-user-button.confirm-alert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
isDeletable = () => {
|
||||
return this.props.user._links.delete;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { confirmDialog, t } = this.props;
|
||||
const action = confirmDialog ? this.confirmDelete : this.deleteUser;
|
||||
|
||||
if (!this.isDeletable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavAction icon="fas fa-times" label={t("delete-user-button.label")} action={action} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("users")(DeleteUserNavLink);
|
||||
@@ -1,82 +0,0 @@
|
||||
import React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import "../../../tests/enzyme";
|
||||
import "../../../tests/i18n";
|
||||
import DeleteUserNavLink from "./DeleteUserNavLink";
|
||||
|
||||
import { confirmAlert } from "@scm-manager/ui-components";
|
||||
jest.mock("@scm-manager/ui-components", () => ({
|
||||
confirmAlert: jest.fn(),
|
||||
NavAction: require.requireActual("@scm-manager/ui-components").NavAction
|
||||
}));
|
||||
|
||||
describe("DeleteUserNavLink", () => {
|
||||
it("should render nothing, if the delete link is missing", () => {
|
||||
const user = {
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<DeleteUserNavLink user={user} deleteUser={() => {}} />
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const user = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/users"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteUserNavLink user={user} deleteUser={() => {}} />
|
||||
);
|
||||
expect(navLink.text()).not.toBe("");
|
||||
});
|
||||
|
||||
it("should open the confirm dialog on navLink click", () => {
|
||||
const user = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/users"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteUserNavLink user={user} deleteUser={() => {}} />
|
||||
);
|
||||
navLink.find("a").simulate("click");
|
||||
|
||||
expect(confirmAlert.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should call the delete user function with delete url", () => {
|
||||
const user = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/users"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let calledUrl = null;
|
||||
function capture(user) {
|
||||
calledUrl = user._links.delete.href;
|
||||
}
|
||||
|
||||
const navLink = mount(
|
||||
<DeleteUserNavLink
|
||||
user={user}
|
||||
confirmDialog={false}
|
||||
deleteUser={capture}
|
||||
/>
|
||||
);
|
||||
navLink.find("a").simulate("click");
|
||||
|
||||
expect(calledUrl).toBe("/users");
|
||||
});
|
||||
});
|
||||
@@ -1,28 +1,28 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
user: User,
|
||||
editUrl: String
|
||||
editUrl: String,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class EditUserNavLink extends React.Component<Props> {
|
||||
isEditable = () => {
|
||||
return this.props.user._links.update;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, editUrl } = this.props;
|
||||
|
||||
if (!this.isEditable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={editUrl} icon="fas fa-cog" label={t("edit-user-button.label")} />;
|
||||
return <NavLink to={editUrl} label={t("singleUser.menu.generalNavLink")} />;
|
||||
}
|
||||
|
||||
isEditable = () => {
|
||||
return this.props.user._links.update;
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("users")(EditUserNavLink);
|
||||
|
||||
@@ -17,7 +17,7 @@ class ChangePasswordNavLink extends React.Component<Props> {
|
||||
if (!this.hasPermissionToSetPassword()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={passwordUrl} label={t("set-password-button.label")} />;
|
||||
return <NavLink to={passwordUrl} label={t("singleUser.menu.setPasswordNavLink")} />;
|
||||
}
|
||||
|
||||
hasPermissionToSetPassword = () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ class ChangePermissionNavLink extends React.Component<Props> {
|
||||
if (!this.hasPermissionToSetPermission()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={permissionsUrl} label={t("set-permissions-button.label")} />;
|
||||
return <NavLink to={permissionsUrl} label={t("singleUser.menu.setPermissionsNavLink")} />;
|
||||
}
|
||||
|
||||
hasPermissionToSetPermission = () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as DeleteUserNavLink } from "./DeleteUserNavLink";
|
||||
export { default as EditUserNavLink } from "./EditUserNavLink";
|
||||
export { default as SetPasswordNavLink } from "./SetPasswordNavLink";
|
||||
export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink";
|
||||
|
||||
@@ -49,8 +49,8 @@ class AddUser extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={t("add-user.title")}
|
||||
subtitle={t("add-user.subtitle")}
|
||||
title={t("addUser.title")}
|
||||
subtitle={t("addUser.subtitle")}
|
||||
error={error}
|
||||
showContentOnError={true}
|
||||
>
|
||||
|
||||
113
scm-ui/src/users/containers/DeleteUser.js
Normal file
113
scm-ui/src/users/containers/DeleteUser.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Subtitle,
|
||||
DeleteButton,
|
||||
confirmAlert,
|
||||
ErrorNotification
|
||||
} from "@scm-manager/ui-components";
|
||||
import {
|
||||
deleteUser,
|
||||
getDeleteUserFailure,
|
||||
isDeleteUserPending
|
||||
} from "../modules/users";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
user: User,
|
||||
confirmDialog?: boolean,
|
||||
deleteUser: (user: User, callback?: () => void) => void,
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class DeleteUser extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
};
|
||||
|
||||
userDeleted = () => {
|
||||
this.props.history.push("/users");
|
||||
};
|
||||
|
||||
deleteUser = () => {
|
||||
this.props.deleteUser(this.props.user, this.userDeleted);
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("deleteUser.confirmAlert.title"),
|
||||
message: t("deleteUser.confirmAlert.message"),
|
||||
buttons: [
|
||||
{
|
||||
label: t("deleteUser.confirmAlert.submit"),
|
||||
onClick: () => this.deleteUser()
|
||||
},
|
||||
{
|
||||
label: t("deleteUser.confirmAlert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
isDeletable = () => {
|
||||
return this.props.user._links.delete;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, error, confirmDialog, t } = this.props;
|
||||
const action = confirmDialog ? this.confirmDelete : this.deleteUser;
|
||||
|
||||
if (!this.isDeletable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("deleteUser.subtitle")} />
|
||||
<ErrorNotification error={error} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<DeleteButton
|
||||
label={t("deleteUser.button")}
|
||||
action={action}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loading = isDeleteUserPending(state, ownProps.user.name);
|
||||
const error = getDeleteUserFailure(state, ownProps.user.name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
deleteUser: (user: User, callback?: () => void) => {
|
||||
dispatch(deleteUser(user, callback));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(translate("users")(DeleteUser)));
|
||||
@@ -2,7 +2,8 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import UserForm from "./../components/UserForm";
|
||||
import UserForm from "../components/UserForm";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
import type { User } from "@scm-manager/ui-types";
|
||||
import {
|
||||
modifyUser,
|
||||
@@ -31,6 +32,7 @@ class EditUser extends React.Component<Props> {
|
||||
const { modifyUserReset, user } = this.props;
|
||||
modifyUserReset(user);
|
||||
}
|
||||
|
||||
userModified = (user: User) => () => {
|
||||
this.props.history.push(`/user/${user.name}`);
|
||||
};
|
||||
@@ -49,11 +51,22 @@ class EditUser extends React.Component<Props> {
|
||||
user={user}
|
||||
loading={loading}
|
||||
/>
|
||||
<hr />
|
||||
<DeleteUser user={user} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loading = isModifyUserPending(state, ownProps.user.name);
|
||||
const error = getModifyUserFailure(state, ownProps.user.name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
modifyUser: (user: User, callback?: () => void) => {
|
||||
@@ -65,15 +78,6 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loading = isModifyUserPending(state, ownProps.user.name);
|
||||
const error = getModifyUserFailure(state, ownProps.user.name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Page,
|
||||
Loading,
|
||||
Navigation,
|
||||
SubNavigation,
|
||||
Section,
|
||||
NavLink,
|
||||
ErrorPage
|
||||
@@ -16,24 +17,16 @@ import type { User } from "@scm-manager/ui-types";
|
||||
import type { History } from "history";
|
||||
import {
|
||||
fetchUserByName,
|
||||
deleteUser,
|
||||
getUserByName,
|
||||
isFetchUserPending,
|
||||
getFetchUserFailure,
|
||||
isDeleteUserPending,
|
||||
getDeleteUserFailure
|
||||
getFetchUserFailure
|
||||
} from "../modules/users";
|
||||
|
||||
import {
|
||||
DeleteUserNavLink,
|
||||
EditUserNavLink,
|
||||
SetPasswordNavLink,
|
||||
SetPermissionsNavLink
|
||||
} from "./../components/navLinks";
|
||||
import { EditUserNavLink, SetPasswordNavLink, SetPermissionsNavLink } from "./../components/navLinks";
|
||||
import { translate } from "react-i18next";
|
||||
import { getUsersLink } from "../../modules/indexResource";
|
||||
import SetUserPassword from "../components/SetUserPassword";
|
||||
import SetPermissions from "../../permissions/components/SetPermissions";
|
||||
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
@@ -42,8 +35,7 @@ type Props = {
|
||||
error: Error,
|
||||
usersLink: string,
|
||||
|
||||
// dispatcher functions
|
||||
deleteUser: (user: User, callback?: () => void) => void,
|
||||
// dispatcher function
|
||||
fetchUserByName: (string, string) => void,
|
||||
|
||||
// context objects
|
||||
@@ -57,14 +49,6 @@ class SingleUser extends React.Component<Props> {
|
||||
this.props.fetchUserByName(this.props.usersLink, this.props.name);
|
||||
}
|
||||
|
||||
userDeleted = () => {
|
||||
this.props.history.push("/users");
|
||||
};
|
||||
|
||||
deleteUser = (user: User) => {
|
||||
this.props.deleteUser(user, this.userDeleted);
|
||||
};
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
return url.substring(0, url.length - 2);
|
||||
@@ -82,8 +66,8 @@ class SingleUser extends React.Component<Props> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("single-user.error-title")}
|
||||
subtitle={t("single-user.error-subtitle")}
|
||||
title={t("singleUser.errorTitle")}
|
||||
subtitle={t("singleUser.errorSubtitle")}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
@@ -95,21 +79,26 @@ class SingleUser extends React.Component<Props> {
|
||||
|
||||
const url = this.matchedUrl();
|
||||
|
||||
const extensionProps = {
|
||||
user,
|
||||
url
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title={user.displayName}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Route path={url} exact component={() => <Details user={user} />} />
|
||||
<Route
|
||||
path={`${url}/edit`}
|
||||
path={`${url}/settings/general`}
|
||||
component={() => <EditUser user={user} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/password`}
|
||||
path={`${url}/settings/password`}
|
||||
component={() => <SetUserPassword user={user} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/permissions`}
|
||||
path={`${url}/settings/permissions`}
|
||||
component={() => (
|
||||
<SetPermissions
|
||||
selectedPermissionsLink={user._links.permissions}
|
||||
@@ -119,25 +108,34 @@ class SingleUser extends React.Component<Props> {
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("single-user.navigation-label")}>
|
||||
<Section label={t("singleUser.menu.navigationLabel")}>
|
||||
<NavLink
|
||||
to={`${url}`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("single-user.information-label")}
|
||||
label={t("singleUser.menu.informationNavLink")}
|
||||
/>
|
||||
<SubNavigation
|
||||
to={`${url}/settings/general`}
|
||||
label={t("singleUser.menu.settingsNavLink")}
|
||||
>
|
||||
<EditUserNavLink
|
||||
user={user}
|
||||
editUrl={`${url}/settings/general`}
|
||||
/>
|
||||
<EditUserNavLink user={user} editUrl={`${url}/edit`} />
|
||||
<SetPasswordNavLink
|
||||
user={user}
|
||||
passwordUrl={`${url}/password`}
|
||||
passwordUrl={`${url}/settings/password`}
|
||||
/>
|
||||
<SetPermissionsNavLink
|
||||
user={user}
|
||||
permissionsUrl={`${url}/permissions`}
|
||||
permissionsUrl={`${url}/settings/permissions`}
|
||||
/>
|
||||
</Section>
|
||||
<Section label={t("single-user.actions-label")}>
|
||||
<DeleteUserNavLink user={user} deleteUser={this.deleteUser} />
|
||||
<NavLink to="/users" icon="fas fa-undo" label={t("single-user.back-label")} />
|
||||
<ExtensionPoint
|
||||
name="user.subnavigation"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</SubNavigation>
|
||||
</Section>
|
||||
</Navigation>
|
||||
</div>
|
||||
@@ -150,10 +148,8 @@ class SingleUser extends React.Component<Props> {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const name = ownProps.match.params.name;
|
||||
const user = getUserByName(state, name);
|
||||
const loading =
|
||||
isFetchUserPending(state, name) || isDeleteUserPending(state, name);
|
||||
const error =
|
||||
getFetchUserFailure(state, name) || getDeleteUserFailure(state, name);
|
||||
const loading = isFetchUserPending(state, name);
|
||||
const error = getFetchUserFailure(state, name);
|
||||
const usersLink = getUsersLink(state);
|
||||
return {
|
||||
usersLink,
|
||||
@@ -168,9 +164,6 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchUserByName: (link: string, name: string) => {
|
||||
dispatch(fetchUserByName(link, name));
|
||||
},
|
||||
deleteUser: (user: User, callback?: () => void) => {
|
||||
dispatch(deleteUser(user, callback));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,10 +14,9 @@ import {
|
||||
getFetchUsersFailure
|
||||
} from "../modules/users";
|
||||
|
||||
import { Page, Paginator } from "@scm-manager/ui-components";
|
||||
import { Page, CreateButton, Paginator } from "@scm-manager/ui-components";
|
||||
import { UserTable } from "./../components/table";
|
||||
import type { User, PagedCollection } from "@scm-manager/ui-types";
|
||||
import CreateUserButton from "../components/buttons/CreateUserButton";
|
||||
import { getUsersLink } from "../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
@@ -86,8 +85,9 @@ class Users extends React.Component<Props> {
|
||||
}
|
||||
|
||||
renderCreateButton() {
|
||||
const { t } = this.props;
|
||||
if (this.props.canAddUsers) {
|
||||
return <CreateUserButton />;
|
||||
return <CreateButton label={t("users.createButton")} link="/users/add" />;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -283,18 +283,29 @@ $fa-font-path: "webfonts";
|
||||
}
|
||||
.menu-list {
|
||||
a {
|
||||
border-radius: 0;
|
||||
color: #333;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #eee;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
|
||||
&.is-active {
|
||||
color: $blue;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
> li {
|
||||
ul {
|
||||
margin: 0;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
li {
|
||||
border-right: none;
|
||||
}
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
> a.is-active:before {
|
||||
position: relative;
|
||||
content: " ";
|
||||
background: $blue;
|
||||
@@ -305,12 +316,43 @@ $fa-font-path: "webfonts";
|
||||
float: left;
|
||||
top: -16px;
|
||||
}
|
||||
|
||||
border-radius: 0;
|
||||
border-top: 1px solid #eee;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
> li:first-child > a {
|
||||
> li:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
li:last-child > a {
|
||||
li:last-child {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.sub-menu li {
|
||||
line-height: 1;
|
||||
|
||||
a {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
a:before {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
line-height: 1;
|
||||
content: "\f105";
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user