mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TopNav: Updates to create service account page and invite user (#52480)
* Simplify logic to support both navs * Added new file
This commit is contained in:
parent
b42ac8a211
commit
01d561224c
10
public/app/core/components/Page/OldNavOnly.tsx
Normal file
10
public/app/core/components/Page/OldNavOnly.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/** Remove after topnav feature toggle is removed */
|
||||
export function OldNavOnly({ children }: Props): React.ReactElement | null {
|
||||
return <>{children}</>;
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, NavModel, NavModelItem } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
||||
|
||||
@ -10,6 +10,7 @@ import { Footer } from '../Footer/Footer';
|
||||
import { PageHeader } from '../PageHeader/PageHeader';
|
||||
import { Page as NewPage } from '../PageNew/Page';
|
||||
|
||||
import { OldNavOnly } from './OldNavOnly';
|
||||
import { PageContents } from './PageContents';
|
||||
import { PageLayoutType, PageType } from './types';
|
||||
import { usePageNav } from './usePageNav';
|
||||
@ -31,7 +32,7 @@ export const OldPage: PageType = ({
|
||||
|
||||
usePageTitle(navModel, pageNav);
|
||||
|
||||
const pageHeaderNav = pageNav ?? navModel?.main;
|
||||
const pageHeaderNav = getPageHeaderNav(navModel, pageNav);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.wrapper, className)}>
|
||||
@ -58,8 +59,17 @@ export const OldPage: PageType = ({
|
||||
);
|
||||
};
|
||||
|
||||
function getPageHeaderNav(navModel?: NavModel, pageNav?: NavModelItem): NavModelItem | undefined {
|
||||
if (pageNav?.children && pageNav.children.length > 0) {
|
||||
return pageNav;
|
||||
}
|
||||
|
||||
return navModel?.main;
|
||||
}
|
||||
|
||||
OldPage.Header = PageHeader;
|
||||
OldPage.Contents = PageContents;
|
||||
OldPage.OldNavOnly = OldNavOnly;
|
||||
|
||||
export const Page: PageType = config.featureToggles.topnav ? NewPage : OldPage;
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { FC, HTMLAttributes, RefCallback } from 'react';
|
||||
import React, { FC, HTMLAttributes, RefCallback } from 'react';
|
||||
|
||||
import { NavModel, NavModelItem } from '@grafana/data';
|
||||
|
||||
import { PageHeader } from '../PageHeader/PageHeader';
|
||||
|
||||
import { OldNavOnly } from './OldNavOnly';
|
||||
import { PageContents } from './PageContents';
|
||||
|
||||
export interface PageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
@ -28,5 +29,6 @@ export enum PageLayoutType {
|
||||
|
||||
export interface PageType extends FC<PageProps> {
|
||||
Header: typeof PageHeader;
|
||||
OldNavOnly: typeof OldNavOnly;
|
||||
Contents: typeof PageContents;
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ export const Page: PageType = ({
|
||||
|
||||
Page.Header = PageHeader;
|
||||
Page.Contents = PageContents;
|
||||
Page.OldNavOnly = () => null;
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const shadow = theme.isDark
|
||||
|
@ -1,18 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { locationUtil } from '@grafana/data';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import {
|
||||
HorizontalGroup,
|
||||
Button,
|
||||
LinkButton,
|
||||
Input,
|
||||
Switch,
|
||||
RadioButtonGroup,
|
||||
Form,
|
||||
Field,
|
||||
InputControl,
|
||||
} from '@grafana/ui';
|
||||
import { Button, LinkButton, Input, Switch, RadioButtonGroup, Form, Field, InputControl, FieldSet } from '@grafana/ui';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { OrgRole, useDispatch } from 'app/types';
|
||||
|
||||
@ -52,32 +43,34 @@ export const UserInviteForm = () => {
|
||||
{({ register, control, errors }) => {
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
invalid={!!errors.loginOrEmail}
|
||||
error={!!errors.loginOrEmail ? 'Email or username is required' : undefined}
|
||||
label="Email or username"
|
||||
>
|
||||
<Input {...register('loginOrEmail', { required: true })} placeholder="email@example.com" />
|
||||
</Field>
|
||||
<Field invalid={!!errors.name} label="Name">
|
||||
<Input {...register('name')} placeholder="(optional)" />
|
||||
</Field>
|
||||
<Field invalid={!!errors.role} label="Role">
|
||||
<InputControl
|
||||
render={({ field: { ref, ...field } }) => <RadioButtonGroup {...field} options={roles} />}
|
||||
control={control}
|
||||
name="role"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Send invite email">
|
||||
<Switch id="send-email-switch" {...register('sendEmail')} />
|
||||
</Field>
|
||||
<HorizontalGroup>
|
||||
<FieldSet>
|
||||
<Field
|
||||
invalid={!!errors.loginOrEmail}
|
||||
error={!!errors.loginOrEmail ? 'Email or username is required' : undefined}
|
||||
label="Email or username"
|
||||
>
|
||||
<Input {...register('loginOrEmail', { required: true })} placeholder="email@example.com" />
|
||||
</Field>
|
||||
<Field invalid={!!errors.name} label="Name">
|
||||
<Input {...register('name')} placeholder="(optional)" />
|
||||
</Field>
|
||||
<Field invalid={!!errors.role} label="Role">
|
||||
<InputControl
|
||||
render={({ field: { ref, ...field } }) => <RadioButtonGroup {...field} options={roles} />}
|
||||
control={control}
|
||||
name="role"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Send invite email">
|
||||
<Switch id="send-email-switch" {...register('sendEmail')} />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
<Stack>
|
||||
<Button type="submit">Submit</Button>
|
||||
<LinkButton href={locationUtil.assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary">
|
||||
Back
|
||||
</LinkButton>
|
||||
</HorizontalGroup>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
@ -1,35 +1,29 @@
|
||||
import React, { FC } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React from 'react';
|
||||
|
||||
import { NavModel } from '@grafana/data';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { StoreState } from 'app/types/store';
|
||||
|
||||
import UserInviteForm from './UserInviteForm';
|
||||
|
||||
interface Props {
|
||||
navModel: NavModel;
|
||||
}
|
||||
export function UserInvitePage() {
|
||||
const subTitle = (
|
||||
<>
|
||||
Send invitation or add existing Grafana user to the organization.
|
||||
<span className="highlight-word"> {contextSrv.user.orgName}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
export const UserInvitePage: FC<Props> = ({ navModel }) => {
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page navId="users" pageNav={{ text: 'Invite user' }} subTitle={subTitle}>
|
||||
<Page.Contents>
|
||||
<h3 className="page-sub-heading">Invite user</h3>
|
||||
<div className="p-b-2">
|
||||
Send invitation or add existing Grafana user to the organization.
|
||||
<span className="highlight-word"> {contextSrv.user.orgName}</span>
|
||||
</div>
|
||||
<Page.OldNavOnly>
|
||||
<h3 className="page-sub-heading">Invite user</h3>
|
||||
<div className="p-b-2">{subTitle}</div>
|
||||
</Page.OldNavOnly>
|
||||
<UserInviteForm />
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
navModel: getNavModel(state.navIndex, 'users'),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(UserInvitePage);
|
||||
export default UserInvitePage;
|
||||
|
@ -36,7 +36,9 @@ export function ChangePasswordPage({ loadUser, isUpdating, user, changePassword
|
||||
<Page.Contents isLoading={!Boolean(user)}>
|
||||
{user ? (
|
||||
<>
|
||||
<h3 className="page-heading">Change Your Password</h3>
|
||||
<Page.OldNavOnly>
|
||||
<h3 className="page-sub-heading">Change Your Password</h3>
|
||||
</Page.OldNavOnly>
|
||||
<ChangePasswordForm user={user} onChangePassword={changePassword} isSaving={isUpdating} />
|
||||
</>
|
||||
) : null}
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { Form, Button, Input, Field } from '@grafana/ui';
|
||||
import { Form, Button, Input, Field, FieldSet } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||
import { fetchBuiltinRoles, fetchRoleOptions, updateUserRoles } from 'app/core/components/RolePicker/api';
|
||||
@ -110,39 +110,43 @@ export const ServiceAccountCreatePage = ({}: Props): JSX.Element => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Page navId="serviceaccounts">
|
||||
<Page navId="serviceaccounts" pageNav={{ text: 'Create service account' }}>
|
||||
<Page.Contents>
|
||||
<h1>Create service account</h1>
|
||||
<Page.OldNavOnly>
|
||||
<h3 className="page-sub-heading">Create service account</h3>
|
||||
</Page.OldNavOnly>
|
||||
<Form onSubmit={onSubmit} validateOn="onSubmit">
|
||||
{({ register, errors }) => {
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
label="Display name"
|
||||
required
|
||||
invalid={!!errors.name}
|
||||
error={errors.name ? 'Display name is required' : undefined}
|
||||
>
|
||||
<Input id="display-name-input" {...register('name', { required: true })} autoFocus />
|
||||
</Field>
|
||||
<Field label="Role">
|
||||
{contextSrv.licensedAccessControlEnabled() ? (
|
||||
<UserRolePicker
|
||||
userId={serviceAccount.id || 0}
|
||||
orgId={serviceAccount.orgId}
|
||||
builtInRole={serviceAccount.role}
|
||||
builtInRoles={builtinRoles}
|
||||
onBuiltinRoleChange={onRoleChange}
|
||||
builtinRolesDisabled={false}
|
||||
roleOptions={roleOptions}
|
||||
updateDisabled={true}
|
||||
onApplyRoles={onPendingRolesUpdate}
|
||||
pendingRoles={pendingRoles}
|
||||
/>
|
||||
) : (
|
||||
<OrgRolePicker aria-label="Role" value={serviceAccount.role} onChange={onRoleChange} />
|
||||
)}
|
||||
</Field>
|
||||
<FieldSet>
|
||||
<Field
|
||||
label="Display name"
|
||||
required
|
||||
invalid={!!errors.name}
|
||||
error={errors.name ? 'Display name is required' : undefined}
|
||||
>
|
||||
<Input id="display-name-input" {...register('name', { required: true })} autoFocus />
|
||||
</Field>
|
||||
<Field label="Role">
|
||||
{contextSrv.licensedAccessControlEnabled() ? (
|
||||
<UserRolePicker
|
||||
userId={serviceAccount.id || 0}
|
||||
orgId={serviceAccount.orgId}
|
||||
builtInRole={serviceAccount.role}
|
||||
builtInRoles={builtinRoles}
|
||||
onBuiltinRoleChange={onRoleChange}
|
||||
builtinRolesDisabled={false}
|
||||
roleOptions={roleOptions}
|
||||
updateDisabled={true}
|
||||
onApplyRoles={onPendingRolesUpdate}
|
||||
pendingRoles={pendingRoles}
|
||||
/>
|
||||
) : (
|
||||
<OrgRolePicker aria-label="Role" value={serviceAccount.role} onChange={onRoleChange} />
|
||||
)}
|
||||
</Field>
|
||||
</FieldSet>
|
||||
<Button type="submit">Create</Button>
|
||||
</>
|
||||
);
|
||||
|
@ -4,7 +4,6 @@ import React, { useEffect, useState } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { GrafanaTheme2, OrgRole } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Alert, ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
@ -193,7 +192,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
onRemove={onMigrationInfoClose}
|
||||
></Alert>
|
||||
)}
|
||||
{!config.featureToggles.topnav && (
|
||||
<Page.OldNavOnly>
|
||||
<div className={styles.pageHeader}>
|
||||
<h2>Service accounts</h2>
|
||||
<div className={styles.apiKeyInfoLabel}>
|
||||
@ -207,7 +206,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
<span>Looking for API keys?</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Page.OldNavOnly>
|
||||
<div className="page-action-bar">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<FilterInput
|
||||
|
Loading…
Reference in New Issue
Block a user