import { Popover, List, ListItem, ListItemIcon, ListItemText, Divider } from "@material-ui/core";
import { DefaultTransformOrigin } from "../popover/helpers";
import { IconType } from "../icon/icon";
+import { RootState } from "store/store";
+import { ContextMenuResource } from "store/context-menu/context-menu-actions";
export interface ContextMenuItem {
name?: string | React.ComponentType;
icon?: IconType;
component?: React.ComponentType<any>;
adminOnly?: boolean;
+ filters?: ((state: RootState, resource: ContextMenuResource) => boolean)[]
}
export type ContextMenuItemGroup = ContextMenuItem[];
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { RootState } from "store/store";
+import { ContextMenuResource } from "store/context-menu/context-menu-actions";
+import { getUserAccountStatus, UserAccountStatus } from "store/users/users-actions";
+import { matchMyAccountRoute, matchUserProfileRoute } from "routes/routes";
+
+export const isAdmin = (state: RootState, resource: ContextMenuResource) => {
+ return state.auth.user!.isAdmin;
+}
+
+export const canActivateUser = (state: RootState, resource: ContextMenuResource) => {
+ const status = getUserAccountStatus(state, resource.uuid);
+ return status === UserAccountStatus.INACTIVE ||
+ status === UserAccountStatus.SETUP;
+};
+
+export const canDeactivateUser = (state: RootState, resource: ContextMenuResource) => {
+ const status = getUserAccountStatus(state, resource.uuid);
+ return status === UserAccountStatus.SETUP ||
+ status === UserAccountStatus.ACTIVE;
+};
+
+export const canSetupUser = (state: RootState, resource: ContextMenuResource) => {
+ const status = getUserAccountStatus(state, resource.uuid);
+ return status === UserAccountStatus.INACTIVE;
+};
+
+export const needsUserProfileLink = (state: RootState, resource: ContextMenuResource) => (
+ state.router.location ?
+ !(matchUserProfileRoute(state.router.location.pathname)
+ || matchMyAccountRoute(state.router.location.pathname)
+ ) : true
+);
+
+export const isOtherUser = (state: RootState, resource: ContextMenuResource) => {
+ return state.auth.user!.uuid !== resource.uuid;
+};
import { startSubmit, reset, stopSubmit } from "redux-form";
import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
import { UserResource } from "models/user";
-import { getResource } from 'store/resources/resources';
+import { filterResources, getResource } from 'store/resources/resources';
import { navigateTo, navigateToUsers, navigateToRootProject } from "store/navigation/navigation-action";
import { authActions } from 'store/auth/auth-action';
import { getTokenV2 } from "models/api-client-authorization";
import { VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD } from "store/virtual-machines/virtual-machines-actions";
import { PermissionLevel } from "models/permission";
import { updateResources } from "store/resources/resources-actions";
+import { BuiltinGroups, getBuiltinGroupUuid } from "models/group";
+import { LinkClass, LinkResource } from "models/link";
+import { ResourceKind } from "models/resource";
export const USERS_PANEL_ID = 'usersPanel';
export const USER_ATTRIBUTES_DIALOG = 'userAttributesDialog';
(dispatch: Dispatch) => {
dispatch(userBindedActions.REQUEST_ITEMS());
};
+
+export enum UserAccountStatus {
+ ACTIVE = 'Active',
+ INACTIVE = 'Inactive',
+ SETUP = 'Setup',
+ }
+
+export const getUserAccountStatus = (state: RootState, uuid: string) => {
+ const user = getResource<UserResource>(uuid)(state.resources);
+ // Get membership links for all users group
+ const allUsersGroupUuid = getBuiltinGroupUuid(state.auth.localCluster, BuiltinGroups.ALL);
+ const permissions = filterResources((resource: LinkResource) =>
+ resource.kind === ResourceKind.LINK &&
+ resource.linkClass === LinkClass.PERMISSION &&
+ resource.headUuid === allUsersGroupUuid &&
+ resource.tailUuid === uuid
+ )(state.resources);
+
+ return user && user.isActive
+ ? UserAccountStatus.ACTIVE
+ : permissions.length > 0
+ ? UserAccountStatus.SETUP
+ : UserAccountStatus.INACTIVE;
+}
import { loginAs, openUserAttributes, openUserProjects } from "store/users/users-actions";
import { openSetupDialog, openDeactivateDialog, openActivateDialog } from "store/user-profile/user-profile-actions";
import { navigateToUserProfile } from "store/navigation/navigation-action";
+import { canActivateUser, canDeactivateUser, canSetupUser, isAdmin, needsUserProfileLink, isOtherUser } from "store/context-menu/context-menu-filters";
export const userActionSet: ContextMenuActionSet = [[{
name: "Attributes",
icon: UserPanelIcon,
execute: (dispatch, { uuid }) => {
dispatch<any>(navigateToUserProfile(uuid));
- }
-},], [{
+ },
+ filters: [needsUserProfileLink]
+}],[{
name: "Activate User",
- adminOnly: true,
icon: ActiveIcon,
execute: (dispatch, { uuid }) => {
dispatch<any>(openActivateDialog(uuid));
- }
-},{
+ },
+ filters: [
+ isAdmin,
+ canActivateUser,
+ ],
+}, {
name: "Setup User",
- adminOnly: true,
icon: AdminMenuIcon,
execute: (dispatch, { uuid }) => {
dispatch<any>(openSetupDialog(uuid));
- }
+ },
+ filters: [
+ isAdmin,
+ canSetupUser,
+ ],
}, {
name: "Deactivate User",
- adminOnly: true,
icon: DeactivateUserIcon,
execute: (dispatch, { uuid }) => {
dispatch<any>(openDeactivateDialog(uuid));
- }
+ },
+ filters: [
+ isAdmin,
+ canDeactivateUser,
+ ],
}, {
name: "Login As User",
- adminOnly: true,
icon: LoginAsIcon,
execute: (dispatch, { uuid }) => {
dispatch<any>(loginAs(uuid));
- }
-},
-
-]];
+ },
+ filters: [
+ isAdmin,
+ isOtherUser,
+ ],
+}]];
type DataProps = Pick<ContextMenuProps, "anchorEl" | "items" | "open"> & { resource?: ContextMenuResource };
const mapStateToProps = (state: RootState): DataProps => {
const { open, position, resource } = state.contextMenu;
- const isAdmin = state.auth.user?.isAdmin;
+
+ const filteredItems = getMenuActionSet(resource).map((group) => (group.filter((item) => {
+ if (resource && item.filters) {
+ // Execute all filters on this item, every returns true IFF all filters return true
+ return item.filters.every((filter) => filter(state, resource));
+ } else {
+ return true;
+ }
+ })));
+
return {
anchorEl: resource ? createAnchorAt(position) : undefined,
- items: getMenuActionSet(resource, isAdmin),
+ items: filteredItems,
open,
resource
};
};
const emptyActionSet: ContextMenuActionSet = [];
-const getMenuActionSet = (resource?: ContextMenuResource, isAdmin?: boolean): ContextMenuActionSet => {
- if (resource) {
- return menuActionSets
- .get(resource.menuKind)!
- .map((group) => (group.filter((item) => (item.adminOnly ? isAdmin : true))))
- || emptyActionSet
- } else {
- return emptyActionSet;
- }
-};
+const getMenuActionSet = (resource?: ContextMenuResource): ContextMenuActionSet => (
+ resource ? menuActionSets.get(resource.menuKind) || emptyActionSet : emptyActionSet
+);
export enum ContextMenuKind {
API_CLIENT_AUTHORIZATION = "ApiClientAuthorization",