Bugfix/link not found (#1296)

Redirect to login page if anonymous tries to access a page without permission

Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2020-08-27 13:20:43 +02:00
committed by GitHub
parent bd81d973ec
commit b4c5f49858
17 changed files with 216 additions and 77 deletions

View File

@@ -29,6 +29,7 @@ import { withI18next } from "storybook-addon-i18next";
import "!style-loader!css-loader!sass-loader!../../ui-styles/src/scm.scss"; import "!style-loader!css-loader!sass-loader!../../ui-styles/src/scm.scss";
import React from "react"; import React from "react";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import withRedux from "./withRedux";
let i18n = i18next; let i18n = i18next;
@@ -70,4 +71,6 @@ addDecorator(
}) })
); );
addDecorator(withRedux);
configure(require.context("../src", true, /\.stories\.tsx?$/), module); configure(require.context("../src", true, /\.stories\.tsx?$/), module);

View File

@@ -0,0 +1,41 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React from "react";
import {createStore} from "redux";
import { Provider } from 'react-redux'
const reducer = (state, action) => {
return state;
};
const withRedux = (storyFn) => {
return React.createElement(Provider, {
store: createStore(reducer, {}),
children: storyFn()
});
}
export default withRedux;

View File

@@ -21,14 +21,24 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { ReactNode } from "react"; import React, { ComponentType, ReactNode } from "react";
import ErrorNotification from "./ErrorNotification"; import ErrorNotification from "./ErrorNotification";
import { MissingLinkError } from "./errors";
import { withContextPath } from "./urls";
import { withRouter, RouteComponentProps } from "react-router-dom";
import ErrorPage from "./ErrorPage";
import { WithTranslation, withTranslation } from "react-i18next";
import { compose } from "redux";
import { connect } from "react-redux";
type Props = { type ExportedProps = {
fallback?: React.ComponentType<any>; fallback?: React.ComponentType<any>;
children: ReactNode; children: ReactNode;
loginLink?: string;
}; };
type Props = WithTranslation & RouteComponentProps & ExportedProps;
type ErrorInfo = { type ErrorInfo = {
componentStack: string; componentStack: string;
}; };
@@ -44,16 +54,44 @@ class ErrorBoundary extends React.Component<Props, State> {
this.state = {}; this.state = {};
} }
componentDidCatch(error: Error, errorInfo: ErrorInfo) { componentDidUpdate(prevProps: Readonly<Props>) {
// Catch errors in any components below and re-render with error message // we must reset the error if the url has changed
this.setState({ if (this.state.error && prevProps.location !== this.props.location) {
error, this.setState({ error: undefined, errorInfo: undefined });
errorInfo }
});
} }
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState(
{
error,
errorInfo
},
() => this.redirectToLogin(error)
);
}
redirectToLogin = (error: Error) => {
const { loginLink } = this.props;
if (error instanceof MissingLinkError) {
if (loginLink) {
window.location.assign(withContextPath("/login"));
}
}
};
renderError = () => { renderError = () => {
const { t } = this.props;
const { error } = this.state;
let FallbackComponent = this.props.fallback; let FallbackComponent = this.props.fallback;
if (error instanceof MissingLinkError) {
return (
<ErrorPage error={error} title={t("errorNotification.prefix")} subtitle={t("errorNotification.forbidden")} />
);
}
if (!FallbackComponent) { if (!FallbackComponent) {
FallbackComponent = ErrorNotification; FallbackComponent = ErrorNotification;
} }
@@ -69,4 +107,17 @@ class ErrorBoundary extends React.Component<Props, State> {
return this.props.children; return this.props.children;
} }
} }
export default ErrorBoundary;
const mapStateToProps = (state: any) => {
const loginLink = state.indexResources?.links?.login?.href;
return {
loginLink
};
};
export default compose<ComponentType<ExportedProps>>(
withRouter,
withTranslation("commons"),
connect(mapStateToProps)
)(ErrorBoundary);

View File

@@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { Component } from "react"; import React, { Component } from "react";
import { Route, Redirect, withRouter, RouteComponentProps, RouteProps } from "react-router-dom"; import { Redirect, Route, RouteComponentProps, RouteProps, withRouter } from "react-router-dom";
type Props = RouteComponentProps & type Props = RouteComponentProps &
RouteProps & { RouteProps & {
@@ -30,7 +30,16 @@ type Props = RouteComponentProps &
}; };
class ProtectedRoute extends Component<Props> { class ProtectedRoute extends Component<Props> {
renderRoute = (Component: any, authenticated?: boolean) => { constructor(props: Props) {
super(props);
this.state = {
error: undefined
};
}
renderRoute = (Component: any) => {
const { authenticated } = this.props;
return (routeProps: any) => { return (routeProps: any) => {
if (authenticated) { if (authenticated) {
return <Component {...routeProps} />; return <Component {...routeProps} />;
@@ -50,8 +59,8 @@ class ProtectedRoute extends Component<Props> {
}; };
render() { render() {
const { component, authenticated, ...routeProps } = this.props; const { component, ...routeProps } = this.props;
return <Route {...routeProps} render={this.renderRoute(component, authenticated)} />; return <Route {...routeProps} render={this.renderRoute(component)} />;
} }
} }

View File

@@ -89,6 +89,10 @@ export class ConflictError extends BackendError {
} }
} }
export class MissingLinkError extends Error {
name = "MissingLinkError";
}
export function createBackendError(content: BackendErrorContent, statusCode: number) { export function createBackendError(content: BackendErrorContent, statusCode: number) {
switch (statusCode) { switch (statusCode) {
case 404: case 404:

View File

@@ -29,12 +29,13 @@ import { Redirect, Route, RouteComponentProps, Switch } from "react-router-dom";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { Links } from "@scm-manager/ui-types"; import { Links } from "@scm-manager/ui-types";
import { import {
CustomQueryFlexWrappedColumns,
NavLink, NavLink,
Page, Page,
CustomQueryFlexWrappedColumns,
PrimaryContentColumn, PrimaryContentColumn,
SecondaryNavigationColumn,
SecondaryNavigation, SecondaryNavigation,
SecondaryNavigationColumn,
StateMenuContextProvider,
SubNavigation SubNavigation
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { getAvailablePluginsLink, getInstalledPluginsLink, getLinks } from "../../modules/indexResource"; import { getAvailablePluginsLink, getInstalledPluginsLink, getLinks } from "../../modules/indexResource";
@@ -44,7 +45,6 @@ import GlobalConfig from "./GlobalConfig";
import RepositoryRoles from "../roles/containers/RepositoryRoles"; import RepositoryRoles from "../roles/containers/RepositoryRoles";
import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole"; import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole"; import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
import { StateMenuContextProvider } from "@scm-manager/ui-components";
type Props = RouteComponentProps & type Props = RouteComponentProps &
WithTranslation & { WithTranslation & {

View File

@@ -26,7 +26,7 @@ import { WithTranslation, withTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Config, NamespaceStrategies } from "@scm-manager/ui-types"; import { Config, NamespaceStrategies } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, Title } from "@scm-manager/ui-components"; import { ErrorNotification, Loading, Title } from "@scm-manager/ui-components";
import { getConfigLink } from "../../modules/indexResource"; import { mustGetConfigLink } from "../../modules/indexResource";
import { import {
fetchConfig, fetchConfig,
getConfig, getConfig,
@@ -186,7 +186,7 @@ const mapStateToProps = (state: any) => {
const config = getConfig(state); const config = getConfig(state);
const configUpdatePermission = getConfigUpdatePermission(state); const configUpdatePermission = getConfigUpdatePermission(state);
const configLink = getConfigLink(state); const configLink = mustGetConfigLink(state);
const namespaceStrategies = getNamespaceStrategies(state); const namespaceStrategies = getNamespaceStrategies(state);
return { return {

View File

@@ -25,7 +25,7 @@ import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import { compose } from "redux"; import { compose } from "redux";
import { PendingPlugins, PluginCollection } from "@scm-manager/ui-types"; import { PendingPlugins, Plugin, PluginCollection } from "@scm-manager/ui-types";
import { import {
Button, Button,
ButtonGroup, ButtonGroup,
@@ -45,16 +45,15 @@ import {
} from "../modules/plugins"; } from "../modules/plugins";
import PluginsList from "../components/PluginList"; import PluginsList from "../components/PluginList";
import { import {
getAvailablePluginsLink, getPendingPluginsLink,
getInstalledPluginsLink, mustGetAvailablePluginsLink,
getPendingPluginsLink mustGetInstalledPluginsLink
} from "../../../modules/indexResource"; } from "../../../modules/indexResource";
import PluginTopActions from "../components/PluginTopActions"; import PluginTopActions from "../components/PluginTopActions";
import PluginBottomActions from "../components/PluginBottomActions"; import PluginBottomActions from "../components/PluginBottomActions";
import ExecutePendingActionModal from "../components/ExecutePendingActionModal"; import ExecutePendingActionModal from "../components/ExecutePendingActionModal";
import CancelPendingActionModal from "../components/CancelPendingActionModal"; import CancelPendingActionModal from "../components/CancelPendingActionModal";
import UpdateAllActionModal from "../components/UpdateAllActionModal"; import UpdateAllActionModal from "../components/UpdateAllActionModal";
import { Plugin } from "@scm-manager/ui-types";
import ShowPendingModal from "../components/ShowPendingModal"; import ShowPendingModal from "../components/ShowPendingModal";
type Props = WithTranslation & { type Props = WithTranslation & {
@@ -319,8 +318,8 @@ const mapStateToProps = (state: any) => {
const collection = getPluginCollection(state); const collection = getPluginCollection(state);
const loading = isFetchPluginsPending(state); const loading = isFetchPluginsPending(state);
const error = getFetchPluginsFailure(state); const error = getFetchPluginsFailure(state);
const availablePluginsLink = getAvailablePluginsLink(state); const availablePluginsLink = mustGetAvailablePluginsLink(state);
const installedPluginsLink = getInstalledPluginsLink(state); const installedPluginsLink = mustGetInstalledPluginsLink(state);
const pendingPluginsLink = getPendingPluginsLink(state); const pendingPluginsLink = getPendingPluginsLink(state);
const pendingPlugins = getPendingPlugins(state); const pendingPlugins = getPendingPlugins(state);

View File

@@ -29,7 +29,7 @@ import { History } from "history";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { RepositoryRole } from "@scm-manager/ui-types"; import { RepositoryRole } from "@scm-manager/ui-types";
import { ErrorPage, Loading, Title } from "@scm-manager/ui-components"; import { ErrorPage, Loading, Title } from "@scm-manager/ui-components";
import { getRepositoryRolesLink } from "../../../modules/indexResource"; import { mustGetRepositoryRolesLink } from "../../../modules/indexResource";
import { fetchRoleByName, getFetchRoleFailure, getRoleByName, isFetchRolePending } from "../modules/roles"; import { fetchRoleByName, getFetchRoleFailure, getRoleByName, isFetchRolePending } from "../modules/roles";
import PermissionRoleDetail from "../components/PermissionRoleDetails"; import PermissionRoleDetail from "../components/PermissionRoleDetails";
import EditRepositoryRole from "./EditRepositoryRole"; import EditRepositoryRole from "./EditRepositoryRole";
@@ -107,7 +107,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const role = getRoleByName(state, roleName); const role = getRoleByName(state, roleName);
const loading = isFetchRolePending(state, roleName); const loading = isFetchRolePending(state, roleName);
const error = getFetchRoleFailure(state, roleName); const error = getFetchRoleFailure(state, roleName);
const repositoryRolesLink = getRepositoryRolesLink(state); const repositoryRolesLink = mustGetRepositoryRolesLink(state);
return { return {
repositoryRolesLink, repositoryRolesLink,
roleName, roleName,

View File

@@ -31,7 +31,7 @@ import Users from "../users/containers/Users";
import Login from "../containers/Login"; import Login from "../containers/Login";
import Logout from "../containers/Logout"; import Logout from "../containers/Logout";
import { ProtectedRoute } from "@scm-manager/ui-components"; import { ProtectedRoute, ErrorBoundary } from "@scm-manager/ui-components";
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import CreateUser from "../users/containers/CreateUser"; import CreateUser from "../users/containers/CreateUser";
@@ -68,6 +68,7 @@ class Main extends React.Component<Props> {
url = "/login"; url = "/login";
} }
return ( return (
<ErrorBoundary>
<div className="main"> <div className="main">
<Switch> <Switch>
<Redirect exact from="/" to={url} /> <Redirect exact from="/" to={url} />
@@ -80,13 +81,13 @@ class Main extends React.Component<Props> {
<ProtectedRoute path="/repo/:namespace/:name" component={RepositoryRoot} authenticated={authenticated} /> <ProtectedRoute path="/repo/:namespace/:name" component={RepositoryRoot} authenticated={authenticated} />
<Redirect exact strict from="/users" to="/users/" /> <Redirect exact strict from="/users" to="/users/" />
<ProtectedRoute exact path="/users/" component={Users} authenticated={authenticated} /> <ProtectedRoute exact path="/users/" component={Users} authenticated={authenticated} />
<ProtectedRoute authenticated={authenticated} path="/users/create" component={CreateUser} /> <ProtectedRoute path="/users/create" component={CreateUser} authenticated={authenticated} />
<ProtectedRoute exact path="/users/:page" component={Users} authenticated={authenticated} /> <ProtectedRoute exact path="/users/:page" component={Users} authenticated={authenticated} />
<ProtectedRoute authenticated={authenticated} path="/user/:name" component={SingleUser} /> <ProtectedRoute path="/user/:name" component={SingleUser} authenticated={authenticated} />
<Redirect exact strict from="/groups" to="/groups/" /> <Redirect exact strict from="/groups" to="/groups/" />
<ProtectedRoute exact path="/groups/" component={Groups} authenticated={authenticated} /> <ProtectedRoute exact path="/groups/" component={Groups} authenticated={authenticated} />
<ProtectedRoute authenticated={authenticated} path="/group/:name" component={SingleGroup} /> <ProtectedRoute path="/group/:name" component={SingleGroup} authenticated={authenticated} />
<ProtectedRoute authenticated={authenticated} path="/groups/create" component={CreateGroup} /> <ProtectedRoute path="/groups/create" component={CreateGroup} authenticated={authenticated} />
<ProtectedRoute exact path="/groups/:page" component={Groups} authenticated={authenticated} /> <ProtectedRoute exact path="/groups/:page" component={Groups} authenticated={authenticated} />
<ProtectedRoute path="/admin" component={Admin} authenticated={authenticated} /> <ProtectedRoute path="/admin" component={Admin} authenticated={authenticated} />
<ProtectedRoute path="/me" component={Profile} authenticated={authenticated} /> <ProtectedRoute path="/me" component={Profile} authenticated={authenticated} />
@@ -94,13 +95,14 @@ class Main extends React.Component<Props> {
name="main.route" name="main.route"
renderAll={true} renderAll={true}
props={{ props={{
authenticated,
me, me,
links links,
authenticated
}} }}
/> />
</Switch> </Switch>
</div> </div>
</ErrorBoundary>
); );
} }
} }

View File

@@ -27,11 +27,10 @@ import { compose } from "redux";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history"; import { History } from "history";
import { DisplayedUser, Group } from "@scm-manager/ui-types"; import { DisplayedUser, Group } from "@scm-manager/ui-types";
import { Page } from "@scm-manager/ui-components"; import { apiClient, Page } from "@scm-manager/ui-components";
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource"; import { getUserAutoCompleteLink, mustGetGroupsLink } from "../../modules/indexResource";
import { createGroup, createGroupReset, getCreateGroupFailure, isCreateGroupPending } from "../modules/groups"; import { createGroup, createGroupReset, getCreateGroupFailure, isCreateGroupPending } from "../modules/groups";
import GroupForm from "../components/GroupForm"; import GroupForm from "../components/GroupForm";
import { apiClient } from "@scm-manager/ui-components";
type Props = WithTranslation & { type Props = WithTranslation & {
createGroup: (link: string, group: Group, callback?: () => void) => void; createGroup: (link: string, group: Group, callback?: () => void) => void;
@@ -97,7 +96,7 @@ const mapDispatchToProps = (dispatch: any) => {
const mapStateToProps = (state: any) => { const mapStateToProps = (state: any) => {
const loading = isCreateGroupPending(state); const loading = isCreateGroupPending(state);
const error = getCreateGroupFailure(state); const error = getCreateGroupFailure(state);
const createLink = getGroupsLink(state); const createLink = mustGetGroupsLink(state);
const autocompleteLink = getUserAutoCompleteLink(state); const autocompleteLink = getUserAutoCompleteLink(state);
return { return {
createLink, createLink,

View File

@@ -35,7 +35,7 @@ import {
PageActions, PageActions,
urls urls
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { getGroupsLink } from "../../modules/indexResource"; import { mustGetGroupsLink } from "../../modules/indexResource";
import { import {
fetchGroupsByPage, fetchGroupsByPage,
getFetchGroupsFailure, getFetchGroupsFailure,
@@ -128,7 +128,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const page = urls.getPageFromMatch(match); const page = urls.getPageFromMatch(match);
const canAddGroups = isPermittedToCreateGroups(state); const canAddGroups = isPermittedToCreateGroups(state);
const list = selectListAsCollection(state); const list = selectListAsCollection(state);
const groupLink = getGroupsLink(state); const groupLink = mustGetGroupsLink(state);
return { return {
groups, groups,

View File

@@ -39,7 +39,7 @@ import {
SubNavigation, SubNavigation,
StateMenuContextProvider StateMenuContextProvider
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { getGroupsLink } from "../../modules/indexResource"; import { getGroupsLink, mustGetGroupsLink } from "../../modules/indexResource";
import { fetchGroupByName, getFetchGroupFailure, getGroupByName, isFetchGroupPending } from "../modules/groups"; import { fetchGroupByName, getFetchGroupFailure, getGroupByName, isFetchGroupPending } from "../modules/groups";
import { Details } from "./../components/table"; import { Details } from "./../components/table";
import { EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks"; import { EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks";
@@ -138,7 +138,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const group = getGroupByName(state, name); const group = getGroupByName(state, name);
const loading = isFetchGroupPending(state, name); const loading = isFetchGroupPending(state, name);
const error = getFetchGroupFailure(state, name); const error = getFetchGroupFailure(state, name);
const groupLink = getGroupsLink(state); const groupLink = mustGetGroupsLink(state);
return { return {
name, name,

View File

@@ -24,7 +24,7 @@
import * as types from "./types"; import * as types from "./types";
import { apiClient } from "@scm-manager/ui-components"; import { apiClient, MissingLinkError } from "@scm-manager/ui-components";
import { Action, IndexResources, Link } from "@scm-manager/ui-types"; import { Action, IndexResources, Link } from "@scm-manager/ui-types";
import { isPending } from "./pending"; import { isPending } from "./pending";
import { getFailure } from "./failure"; import { getFailure } from "./failure";
@@ -123,6 +123,14 @@ export function getLink(state: object, name: string) {
} }
} }
export function mustGetLink(state: object, name: string) {
const link = getLink(state, name);
if (link) {
return link;
}
throw new MissingLinkError(`No link in state for link name: '${name}'`);
}
export function getLinkCollection(state: object, name: string): Link[] { export function getLinkCollection(state: object, name: string): Link[] {
// @ts-ignore Right types not available // @ts-ignore Right types not available
if (state.indexResources.links && state.indexResources.links[name]) { if (state.indexResources.links && state.indexResources.links[name]) {
@@ -145,10 +153,18 @@ export function getAvailablePluginsLink(state: object) {
return getLink(state, "availablePlugins"); return getLink(state, "availablePlugins");
} }
export function mustGetAvailablePluginsLink(state: object) {
return mustGetLink(state, "availablePlugins");
}
export function getInstalledPluginsLink(state: object) { export function getInstalledPluginsLink(state: object) {
return getLink(state, "installedPlugins"); return getLink(state, "installedPlugins");
} }
export function mustGetInstalledPluginsLink(state: object) {
return mustGetLink(state, "installedPlugins");
}
export function getPendingPluginsLink(state: object) { export function getPendingPluginsLink(state: object) {
return getLink(state, "pendingPlugins"); return getLink(state, "pendingPlugins");
} }
@@ -169,10 +185,18 @@ export function getUsersLink(state: object) {
return getLink(state, "users"); return getLink(state, "users");
} }
export function mustGetUsersLink(state: object) {
return mustGetLink(state, "users");
}
export function getRepositoryRolesLink(state: object) { export function getRepositoryRolesLink(state: object) {
return getLink(state, "repositoryRoles"); return getLink(state, "repositoryRoles");
} }
export function mustGetRepositoryRolesLink(state: object) {
return mustGetLink(state, "repositoryRoles");
}
export function getRepositoryVerbsLink(state: object) { export function getRepositoryVerbsLink(state: object) {
return getLink(state, "repositoryVerbs"); return getLink(state, "repositoryVerbs");
} }
@@ -181,10 +205,18 @@ export function getGroupsLink(state: object) {
return getLink(state, "groups"); return getLink(state, "groups");
} }
export function mustGetGroupsLink(state: object) {
return mustGetLink(state, "groups");
}
export function getConfigLink(state: object) { export function getConfigLink(state: object) {
return getLink(state, "config"); return getLink(state, "config");
} }
export function mustGetConfigLink(state: object) {
return mustGetLink(state, "config");
}
export function getRepositoriesLink(state: object) { export function getRepositoriesLink(state: object) {
return getLink(state, "repositories"); return getLink(state, "repositories");
} }

View File

@@ -28,7 +28,7 @@ import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history"; import { History } from "history";
import { User } from "@scm-manager/ui-types"; import { User } from "@scm-manager/ui-types";
import { Page } from "@scm-manager/ui-components"; import { Page } from "@scm-manager/ui-components";
import { getUsersLink } from "../../modules/indexResource"; import { mustGetUsersLink } from "../../modules/indexResource";
import { createUser, createUserReset, getCreateUserFailure, isCreateUserPending } from "../modules/users"; import { createUser, createUserReset, getCreateUserFailure, isCreateUserPending } from "../modules/users";
import UserForm from "../components/UserForm"; import UserForm from "../components/UserForm";
@@ -84,7 +84,7 @@ const mapDispatchToProps = (dispatch: any) => {
const mapStateToProps = (state: any) => { const mapStateToProps = (state: any) => {
const loading = isCreateUserPending(state); const loading = isCreateUserPending(state);
const error = getCreateUserFailure(state); const error = getCreateUserFailure(state);
const usersLink = getUsersLink(state); const usersLink = mustGetUsersLink(state);
return { return {
usersLink, usersLink,
loading, loading,

View File

@@ -43,10 +43,9 @@ import EditUser from "./EditUser";
import { fetchUserByName, getFetchUserFailure, getUserByName, isFetchUserPending } from "../modules/users"; import { fetchUserByName, getFetchUserFailure, getUserByName, isFetchUserPending } from "../modules/users";
import { EditUserNavLink, SetPasswordNavLink, SetPermissionsNavLink, SetPublicKeysNavLink } from "./../components/navLinks"; import { EditUserNavLink, SetPasswordNavLink, SetPermissionsNavLink, SetPublicKeysNavLink } from "./../components/navLinks";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import { getUsersLink } from "../../modules/indexResource"; import { mustGetUsersLink } from "../../modules/indexResource";
import SetUserPassword from "../components/SetUserPassword"; import SetUserPassword from "../components/SetUserPassword";
import SetPermissions from "../../permissions/components/SetPermissions"; import SetPermissions from "../../permissions/components/SetPermissions";
import AddPublicKey from "../components/publicKeys/AddPublicKey";
import SetPublicKeys from "../components/publicKeys/SetPublicKeys"; import SetPublicKeys from "../components/publicKeys/SetPublicKeys";
type Props = RouteComponentProps & type Props = RouteComponentProps &
@@ -148,7 +147,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const user = getUserByName(state, name); const user = getUserByName(state, name);
const loading = isFetchUserPending(state, name); const loading = isFetchUserPending(state, name);
const error = getFetchUserFailure(state, name); const error = getFetchUserFailure(state, name);
const usersLink = getUsersLink(state); const usersLink = mustGetUsersLink(state);
return { return {
usersLink, usersLink,
name, name,

View File

@@ -35,7 +35,7 @@ import {
PageActions, PageActions,
urls urls
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { getUsersLink } from "../../modules/indexResource"; import { mustGetUsersLink } from "../../modules/indexResource";
import { import {
fetchUsersByPage, fetchUsersByPage,
getFetchUsersFailure, getFetchUsersFailure,
@@ -129,7 +129,7 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const page = urls.getPageFromMatch(match); const page = urls.getPageFromMatch(match);
const canAddUsers = isPermittedToCreateUsers(state); const canAddUsers = isPermittedToCreateUsers(state);
const list = selectListAsCollection(state); const list = selectListAsCollection(state);
const usersLink = getUsersLink(state); const usersLink = mustGetUsersLink(state);
return { return {
users, users,