Commit d2689b6d by Tobias Skarhed Committed by GitHub

Migration: User invite (#22263)

* Add new form and functionality

* Add new files

* Connect to Redux and add navigation stuff

* Add required login/name

* Remove import

* Fix feedback

* Replace direct button spacing with HorizontalGroup

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
parent 9cf84c51
......@@ -2,13 +2,17 @@ import { getFormStyles } from './getFormStyles';
import { Label } from './Label';
import { Input } from './Input/Input';
import { ButtonSelect } from './Select/ButtonSelect';
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
import { AsyncSelect, Select } from './Select/Select';
import { Form } from './Form';
import { Field } from './Field';
import { Button, LinkButton } from './Button';
import { Switch } from './Switch';
import { Controller as InputControl } from 'react-hook-form';
const Forms = {
RadioButtonGroup,
Switch,
getFormStyles,
Label,
Input,
......
import coreModule from 'app/core/core_module';
import { getBackendSrv } from '@grafana/runtime';
import { NavModelSrv } from 'app/core/core';
import { ILocationService, IScope } from 'angular';
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
export class UserInviteCtrl {
navModel: any;
invite: any;
inviteForm: any;
/** @ngInject */
constructor(private $scope: IScope, navModelSrv: NavModelSrv, private $location: ILocationService) {
this.navModel = navModelSrv.getNav('cfg', 'users', 0);
this.invite = {
name: '',
email: '',
role: 'Editor',
sendEmail: true,
};
}
sendInvite() {
if (!this.inviteForm.$valid) {
return;
}
promiseToDigest(this.$scope)(
getBackendSrv()
.post('/api/org/invites', this.invite)
.then(() => {
this.$location.path('org/users/');
})
);
}
}
coreModule.controller('UserInviteCtrl', UserInviteCtrl);
import React, { FC } from 'react';
import { Forms, HorizontalGroup } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { OrgRole } from 'app/types';
import { getBackendSrv } from '@grafana/runtime';
import { updateLocation } from 'app/core/actions';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import { appEvents } from 'app/core/core';
import { AppEvents } from '@grafana/data';
import { assureBaseUrl } from 'app/core/utils/location_util';
const roles = [
{ label: 'Viewer', value: OrgRole.Viewer },
{ label: 'Editor', value: OrgRole.Editor },
{ label: 'Admin', value: OrgRole.Admin },
];
interface FormModel {
role: OrgRole;
name: string;
loginOrEmail?: string;
sendEmail: boolean;
email: string;
}
interface Props {
updateLocation: typeof updateLocation;
}
export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
const onSubmit = async (formData: FormModel) => {
try {
await getBackendSrv().post('/api/org/invites', formData);
} catch (err) {
appEvents.emit(AppEvents.alertError, ['Failed to send invite', err.message]);
}
updateLocation({ path: 'org/users/' });
};
const defaultValues: FormModel = {
name: '',
email: '',
role: OrgRole.Editor,
sendEmail: true,
};
return (
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}>
{({ register, control, errors }) => {
return (
<>
<Forms.Field
invalid={!!errors.loginOrEmail}
error={!!errors.loginOrEmail && 'Email or Username is required'}
label="Email or Username"
>
<Forms.Input
size="md"
name="loginOrEmail"
placeholder="email@example.com"
ref={register({ required: true })}
/>
</Forms.Field>
<Forms.Field invalid={!!errors.name} label="Name">
<Forms.Input size="md" name="name" placeholder="(optional)" ref={register} />
</Forms.Field>
<Forms.Field invalid={!!errors.role} label="Role">
<Forms.InputControl as={Forms.RadioButtonGroup} control={control} options={roles} name="role" />
</Forms.Field>
<Forms.Field invalid={!!errors.sendEmail} label="Send invite email">
<Forms.Switch name="sendEmail" ref={register} />
</Forms.Field>
<HorizontalGroup>
<Forms.Button type="submit">Submit</Forms.Button>
<Forms.LinkButton href={assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary">
Back
</Forms.LinkButton>
</HorizontalGroup>
</>
);
}}
</Forms.Form>
);
};
const mapDispatchToProps = {
updateLocation,
};
export default hot(module)(connect(null, mapDispatchToProps)(UserInviteForm));
import React, { FC } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import UserInviteForm from './UserInviteForm';
import { contextSrv, NavModel } from 'app/core/core';
import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState } from 'app/types/store';
import Page from 'app/core/components/Page/Page';
interface Props {
navModel: NavModel;
}
export const UserInvitePage: FC<Props> = ({ navModel }) => {
return (
<Page navModel={navModel}>
<Page.Contents>
<h3 className="page-sub-heading">Invite User</h3>
<div className="p-b-2">
Send invite or add existing Grafana user to the organization
<span className="highlight-word"> {contextSrv.user.orgName}</span>
</div>
<UserInviteForm />
</Page.Contents>
</Page>
);
};
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'users'),
});
export default hot(module)(connect(mapStateToProps)(UserInvitePage));
import './SelectOrgCtrl';
import './NewOrgCtrl';
import './UserInviteCtrl';
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body" ng-cloak>
<h3 class="page-sub-heading">Invite User</h3>
<div class="p-b-2">
Send invite or add existing Grafana user to the organization
<span class="highlight-word">{{contextSrv.user.orgName}}</span>
</div>
<form name="ctrl.inviteForm">
<div class="gf-form-group">
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Email or Username</span>
<input type="text" ng-model="ctrl.invite.loginOrEmail" required class="gf-form-input" placeholder="email@test.com">
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Name</span>
<input type="text" ng-model="ctrl.invite.name" class="gf-form-input" placeholder="name (optional)">
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Role</span>
<select ng-model="ctrl.invite.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']">
</select>
</div>
<gf-form-switch class="gf-form" label="Send invite email" checked="ctrl.invite.sendEmail" label-class="width-10"></gf-form-switch>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-primary" ng-click="ctrl.sendInvite();">Invite</button>
<a class="btn btn-inverse" href="org/users">Back</a>
</div>
</form>
</div>
......@@ -227,9 +227,11 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
},
})
.when('/org/users/invite', {
templateUrl: 'public/app/features/org/partials/invite.html',
controller: 'UserInviteCtrl',
controllerAs: 'ctrl',
template: '<react-container/>',
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "UserInvitePage" */ 'app/features/org/UserInvitePage')),
},
})
.when('/org/apikeys', {
template: '<react-container />',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment