mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Admin: Create/Edit Team/ServiceAccount UI changes (#53889)
* RolePicker: Handle inherited with * Small ammendment to Create Service Account layout * RolePicker: introduce maxWidth prop * Clean up * Change VerticalGroup spacing to large on Team Settings page * Introduce constant for submenu width * Update public/app/core/components/RolePicker/RolePicker.tsx Simplify style parameter Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Add description to the improved calculation Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
parent
9f8cb17b01
commit
aace0b1e7f
@ -5,7 +5,7 @@ import { Role, OrgRole } from 'app/types';
|
||||
|
||||
import { RolePickerInput } from './RolePickerInput';
|
||||
import { RolePickerMenu } from './RolePickerMenu';
|
||||
import { MENU_MAX_HEIGHT, ROLE_PICKER_WIDTH } from './constants';
|
||||
import { MENU_MAX_HEIGHT, ROLE_PICKER_SUBMENU_MIN_WIDTH, ROLE_PICKER_WIDTH } from './constants';
|
||||
|
||||
export interface Props {
|
||||
basicRole?: OrgRole;
|
||||
@ -22,6 +22,7 @@ export interface Props {
|
||||
* Set {@link RolePickerMenu}'s button to display either `Apply` (apply=true) or `Update` (apply=false)
|
||||
*/
|
||||
apply?: boolean;
|
||||
maxWidth?: string | number;
|
||||
}
|
||||
|
||||
export const RolePicker = ({
|
||||
@ -36,6 +37,7 @@ export const RolePicker = ({
|
||||
onBasicRoleChange,
|
||||
canUpdateRoles = true,
|
||||
apply = false,
|
||||
maxWidth = ROLE_PICKER_WIDTH,
|
||||
}: Props): JSX.Element | null => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [selectedRoles, setSelectedRoles] = useState<Role[]>(appliedRoles);
|
||||
@ -54,7 +56,7 @@ export const RolePicker = ({
|
||||
if (!dimensions || !isOpen) {
|
||||
return;
|
||||
}
|
||||
const { bottom, top, left, right } = dimensions;
|
||||
const { bottom, top, left, right, width: currentRolePickerWidth } = dimensions;
|
||||
const distance = window.innerHeight - bottom;
|
||||
const offsetVertical = bottom - top + 10; // Add extra 10px to offset to account for border and outline
|
||||
const offsetHorizontal = right - left;
|
||||
@ -65,7 +67,17 @@ export const RolePicker = ({
|
||||
vertical = offsetVertical;
|
||||
}
|
||||
|
||||
if (window.innerWidth - right < ROLE_PICKER_WIDTH) {
|
||||
/*
|
||||
* This expression calculates whether there is enough place
|
||||
* on the right of the RolePicker input to show/fit the role picker menu and its sub menu AND
|
||||
* whether there is enough place under the RolePicker input to show/fit
|
||||
* both (the role picker menu and its sub menu) aligned to the left edge of the input.
|
||||
* Otherwise, it aligns the role picker menu to the right.
|
||||
*/
|
||||
if (
|
||||
window.innerWidth - right < currentRolePickerWidth &&
|
||||
currentRolePickerWidth < 2 * ROLE_PICKER_SUBMENU_MIN_WIDTH
|
||||
) {
|
||||
horizontal = offsetHorizontal;
|
||||
}
|
||||
|
||||
@ -140,7 +152,14 @@ export const RolePicker = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="role-picker" style={{ position: 'relative', width: ROLE_PICKER_WIDTH }} ref={ref}>
|
||||
<div
|
||||
data-testid="role-picker"
|
||||
style={{
|
||||
position: 'relative',
|
||||
maxWidth,
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<ClickOutsideWrapper onClick={onClickOutside}>
|
||||
<RolePickerInput
|
||||
basicRole={selectedBuiltInRole}
|
||||
|
@ -135,7 +135,7 @@ const getRolePickerInputStyles = (
|
||||
`,
|
||||
disabled && styles.inputDisabled,
|
||||
css`
|
||||
width: ${ROLE_PICKER_WIDTH}px;
|
||||
min-width: ${ROLE_PICKER_WIDTH}px;
|
||||
min-height: 32px;
|
||||
height: auto;
|
||||
flex-direction: row;
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
|
||||
import { OrgRole, Role } from 'app/types';
|
||||
|
||||
import { MENU_MAX_HEIGHT } from './constants';
|
||||
import { MENU_MAX_HEIGHT, ROLE_PICKER_SUBMENU_MIN_WIDTH } from './constants';
|
||||
|
||||
enum GroupType {
|
||||
fixed = 'fixed',
|
||||
@ -588,7 +588,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
min-width: auto;
|
||||
`,
|
||||
menu: css`
|
||||
min-width: 260px;
|
||||
min-width: ${ROLE_PICKER_SUBMENU_MIN_WIDTH}px;
|
||||
|
||||
& > div {
|
||||
padding-top: ${theme.spacing(1)};
|
||||
@ -600,7 +600,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
`,
|
||||
subMenu: css`
|
||||
height: 100%;
|
||||
min-width: 260px;
|
||||
min-width: ${ROLE_PICKER_SUBMENU_MIN_WIDTH}px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid ${theme.components.input.borderColor};
|
||||
|
@ -26,6 +26,7 @@ export interface Props {
|
||||
* @default false
|
||||
*/
|
||||
apply?: boolean;
|
||||
maxWidth?: string | number;
|
||||
}
|
||||
|
||||
export const TeamRolePicker: FC<Props> = ({
|
||||
@ -35,6 +36,7 @@ export const TeamRolePicker: FC<Props> = ({
|
||||
onApplyRoles,
|
||||
pendingRoles,
|
||||
apply = false,
|
||||
maxWidth,
|
||||
}) => {
|
||||
const [{ loading, value: appliedRoles = [] }, getTeamRoles] = useAsyncFn(async () => {
|
||||
try {
|
||||
@ -78,6 +80,7 @@ export const TeamRolePicker: FC<Props> = ({
|
||||
disabled={disabled}
|
||||
basicRoleDisabled={true}
|
||||
canUpdateRoles={canUpdateRoles}
|
||||
maxWidth={maxWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -29,6 +29,7 @@ export interface Props {
|
||||
apply?: boolean;
|
||||
onApplyRoles?: (newRoles: Role[], userId: number, orgId: number | undefined) => void;
|
||||
pendingRoles?: Role[];
|
||||
maxWidth?: string | number;
|
||||
}
|
||||
|
||||
export const UserRolePicker: FC<Props> = ({
|
||||
@ -42,6 +43,7 @@ export const UserRolePicker: FC<Props> = ({
|
||||
apply = false,
|
||||
onApplyRoles,
|
||||
pendingRoles,
|
||||
maxWidth,
|
||||
}) => {
|
||||
const [{ loading, value: appliedRoles = [] }, getUserRoles] = useAsyncFn(async () => {
|
||||
try {
|
||||
@ -92,6 +94,7 @@ export const UserRolePicker: FC<Props> = ({
|
||||
showBasicRole
|
||||
apply={apply}
|
||||
canUpdateRoles={canUpdateRoles}
|
||||
maxWidth={maxWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,2 +1,3 @@
|
||||
export const MENU_MAX_HEIGHT = 300; // max height for the picker's dropdown menu
|
||||
export const ROLE_PICKER_WIDTH = 360;
|
||||
export const ROLE_PICKER_SUBMENU_MIN_WIDTH = 260;
|
||||
|
@ -127,6 +127,7 @@ export const ServiceAccountCreatePage = ({}: Props): JSX.Element => {
|
||||
roleOptions={roleOptions}
|
||||
onApplyRoles={onPendingRolesUpdate}
|
||||
pendingRoles={pendingRoles}
|
||||
maxWidth="100%"
|
||||
/>
|
||||
) : (
|
||||
<OrgRolePicker aria-label="Role" value={serviceAccount.role} onChange={onRoleChange} />
|
||||
|
@ -23,7 +23,7 @@ export const ServiceAccountRoleRow = ({ label, serviceAccount, roleOptions, onRo
|
||||
<Label htmlFor={inputId}>{label}</Label>
|
||||
</td>
|
||||
{contextSrv.licensedAccessControlEnabled() ? (
|
||||
<td className="width-25" colSpan={3}>
|
||||
<td colSpan={3}>
|
||||
<UserRolePicker
|
||||
userId={serviceAccount.id}
|
||||
orgId={serviceAccount.orgId}
|
||||
|
@ -45,7 +45,7 @@ export const CreateTeam = (): JSX.Element => {
|
||||
{({ register, errors }) => (
|
||||
<FieldSet label="New Team">
|
||||
<Field label="Name" required invalid={!!errors.name} error="Team name is required">
|
||||
<Input {...register('name', { required: true })} id="team-name" width={60} />
|
||||
<Input {...register('name', { required: true })} id="team-name" />
|
||||
</Field>
|
||||
{contextSrv.licensedAccessControlEnabled() && (
|
||||
<Field label="Role">
|
||||
@ -56,6 +56,7 @@ export const CreateTeam = (): JSX.Element => {
|
||||
apply={true}
|
||||
onApplyRoles={setPendingRoles}
|
||||
pendingRoles={pendingRoles}
|
||||
maxWidth="100%"
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
@ -63,7 +64,7 @@ export const CreateTeam = (): JSX.Element => {
|
||||
label={'Email'}
|
||||
description={'This is optional and is primarily used for allowing custom team avatars.'}
|
||||
>
|
||||
<Input {...register('email')} type="email" id="team-email" placeholder="email@test.com" width={60} />
|
||||
<Input {...register('email')} type="email" id="team-email" placeholder="email@test.com" />
|
||||
</Field>
|
||||
<div className="gf-form-button-row">
|
||||
<Button type="submit" variant="primary">
|
||||
|
@ -34,57 +34,56 @@ export const TeamSettings: FC<Props> = ({ team, updateTeam }) => {
|
||||
contextSrv.hasPermission(AccessControlAction.ActionUserRolesRemove);
|
||||
|
||||
return (
|
||||
<VerticalGroup>
|
||||
<FieldSet label="Team details">
|
||||
<Form
|
||||
defaultValues={{ ...team }}
|
||||
onSubmit={async (formTeam: Team) => {
|
||||
if (contextSrv.licensedAccessControlEnabled() && canUpdateRoles) {
|
||||
await updateTeamRoles(pendingRoles, team.id);
|
||||
}
|
||||
updateTeam(formTeam.name, formTeam.email);
|
||||
}}
|
||||
disabled={!canWriteTeamSettings}
|
||||
>
|
||||
{({ register, errors }) => (
|
||||
<>
|
||||
<Field
|
||||
label="Name"
|
||||
disabled={!canWriteTeamSettings}
|
||||
required
|
||||
invalid={!!errors.name}
|
||||
error="Name is required"
|
||||
>
|
||||
<Input {...register('name', { required: true })} id="name-input" />
|
||||
</Field>
|
||||
<VerticalGroup spacing="lg">
|
||||
<Form
|
||||
defaultValues={{ ...team }}
|
||||
onSubmit={async (formTeam: Team) => {
|
||||
if (contextSrv.licensedAccessControlEnabled() && canUpdateRoles) {
|
||||
await updateTeamRoles(pendingRoles, team.id);
|
||||
}
|
||||
updateTeam(formTeam.name, formTeam.email);
|
||||
}}
|
||||
disabled={!canWriteTeamSettings}
|
||||
>
|
||||
{({ register, errors }) => (
|
||||
<FieldSet label="Team details">
|
||||
<Field
|
||||
label="Name"
|
||||
disabled={!canWriteTeamSettings}
|
||||
required
|
||||
invalid={!!errors.name}
|
||||
error="Name is required"
|
||||
>
|
||||
<Input {...register('name', { required: true })} id="name-input" />
|
||||
</Field>
|
||||
|
||||
{contextSrv.licensedAccessControlEnabled() && (
|
||||
<Field label="Role">
|
||||
<TeamRolePicker
|
||||
teamId={team.id}
|
||||
roleOptions={roleOptions}
|
||||
disabled={false}
|
||||
apply={true}
|
||||
onApplyRoles={setPendingRoles}
|
||||
pendingRoles={pendingRoles}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field
|
||||
label="Email"
|
||||
description="This is optional and is primarily used to set the team profile avatar (via gravatar service)."
|
||||
disabled={!canWriteTeamSettings}
|
||||
>
|
||||
<Input {...register('email')} placeholder="team@email.com" type="email" id="email-input" />
|
||||
{contextSrv.licensedAccessControlEnabled() && (
|
||||
<Field label="Role">
|
||||
<TeamRolePicker
|
||||
teamId={team.id}
|
||||
roleOptions={roleOptions}
|
||||
disabled={false}
|
||||
apply={true}
|
||||
onApplyRoles={setPendingRoles}
|
||||
pendingRoles={pendingRoles}
|
||||
maxWidth="100%"
|
||||
/>
|
||||
</Field>
|
||||
<Button type="submit" disabled={!canWriteTeamSettings}>
|
||||
Update
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</FieldSet>
|
||||
)}
|
||||
|
||||
<Field
|
||||
label="Email"
|
||||
description="This is optional and is primarily used to set the team profile avatar (via gravatar service)."
|
||||
disabled={!canWriteTeamSettings}
|
||||
>
|
||||
<Input {...register('email')} placeholder="team@email.com" type="email" id="email-input" />
|
||||
</Field>
|
||||
<Button type="submit" disabled={!canWriteTeamSettings}>
|
||||
Update
|
||||
</Button>
|
||||
</FieldSet>
|
||||
)}
|
||||
</Form>
|
||||
<SharedPreferences resourceUri={`teams/${team.id}`} disabled={!canWriteTeamSettings} />
|
||||
</VerticalGroup>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user