mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
Remember current standard editor tab (#53644)
* Remember current standard editor tab * review
This commit is contained in:
@@ -112,13 +112,10 @@ test('rendering and switching tabs for new role', async () => {
|
||||
expect(screen.getByRole('button', { name: 'Create Role' })).toBeEnabled();
|
||||
|
||||
await user.click(getStandardEditorTab());
|
||||
await screen.findByLabelText('Role Name *');
|
||||
await screen.findByLabelText('Max Session TTL');
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /Reset to Standard Settings/i })
|
||||
).not.toBeInTheDocument();
|
||||
await forwardToTab('Resources');
|
||||
await forwardToTab('Admin Rules');
|
||||
await forwardToTab('Options');
|
||||
expect(screen.getByRole('button', { name: 'Create Role' })).toBeEnabled();
|
||||
}, 10000);
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { produce } from 'immer';
|
||||
import { useCallback, useId, useState } from 'react';
|
||||
import { useCallback, useId } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Box, ButtonPrimary, ButtonSecondary, Flex } from 'design';
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
OptionsModel,
|
||||
roleEditorModelToRole,
|
||||
StandardEditorModel,
|
||||
StandardEditorTab,
|
||||
} from './standardmodel';
|
||||
import { ActionType, StandardModelDispatcher } from './useStandardModel';
|
||||
|
||||
@@ -60,20 +60,10 @@ export const StandardEditor = ({
|
||||
dispatch,
|
||||
}: StandardEditorProps) => {
|
||||
const isEditing = !!originalRole;
|
||||
const { roleModel, validationResult } = standardEditorModel;
|
||||
const { roleModel, validationResult, currentTab, disabledTabs } =
|
||||
standardEditorModel;
|
||||
const modelValid = validationResult.isValid;
|
||||
|
||||
enum StandardEditorTab {
|
||||
Overview,
|
||||
Resources,
|
||||
AdminRules,
|
||||
Options,
|
||||
}
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(StandardEditorTab.Overview);
|
||||
const [disabledTabs, setDisabledTabs] = useState(
|
||||
isEditing ? [false, false, false, false] : [false, true, true, true]
|
||||
);
|
||||
const idPrefix = useId();
|
||||
|
||||
const validator = useValidation();
|
||||
@@ -104,6 +94,13 @@ export const StandardEditor = ({
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setCurrentTab = useCallback(
|
||||
(newTab: StandardEditorTab) => {
|
||||
dispatch({ type: ActionType.SetCurrentTab, payload: newTab });
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const validateAndGoToNextTab = useCallback(() => {
|
||||
const nextTabIndex = currentTab + 1;
|
||||
const valid = validate();
|
||||
@@ -112,12 +109,7 @@ export const StandardEditor = ({
|
||||
}
|
||||
validator.reset();
|
||||
setCurrentTab(nextTabIndex);
|
||||
setDisabledTabs(prevEnabledTabs =>
|
||||
produce(prevEnabledTabs, et => {
|
||||
et[nextTabIndex] = false;
|
||||
})
|
||||
);
|
||||
}, [currentTab, validate, validator, setCurrentTab, setDisabledTabs]);
|
||||
}, [currentTab, validate, validator, setCurrentTab]);
|
||||
|
||||
const goToPreviousTab = useCallback(
|
||||
() => setCurrentTab(currentTab - 1),
|
||||
@@ -136,7 +128,7 @@ export const StandardEditor = ({
|
||||
return {
|
||||
key: tab,
|
||||
title: tabTitles[tab],
|
||||
disabled: disabledTabs[tab],
|
||||
disabled: disabledTabs.has(tab),
|
||||
controls: tabElementIDs[tab],
|
||||
status: error ? validationErrorTabStatus : undefined,
|
||||
};
|
||||
|
||||
@@ -53,6 +53,13 @@ import {
|
||||
import { RoleEditorModelValidationResult } from './validation';
|
||||
import { defaultOptions } from './withDefaults';
|
||||
|
||||
export enum StandardEditorTab {
|
||||
Overview,
|
||||
Resources,
|
||||
AdminRules,
|
||||
Options,
|
||||
}
|
||||
|
||||
export type StandardEditorModel = {
|
||||
/**
|
||||
* The role model. Can be undefined if there was an unhandled error when
|
||||
@@ -70,6 +77,8 @@ export type StandardEditorModel = {
|
||||
* role.
|
||||
*/
|
||||
validationResult?: RoleEditorModelValidationResult;
|
||||
currentTab: StandardEditorTab;
|
||||
disabledTabs: Set<StandardEditorTab>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { current, original } from 'immer';
|
||||
import { current, enableMapSet, original } from 'immer';
|
||||
import { Dispatch } from 'react';
|
||||
import { useImmerReducer } from 'use-immer';
|
||||
|
||||
@@ -38,11 +38,15 @@ import {
|
||||
RoleEditorModel,
|
||||
roleToRoleEditorModel,
|
||||
StandardEditorModel,
|
||||
StandardEditorTab,
|
||||
} from './standardmodel';
|
||||
import { validateRoleEditorModel } from './validation';
|
||||
|
||||
const logger = new Logger('useStandardModel');
|
||||
|
||||
// Enable support for the Set type in Immer. We use it for `disabledTabs`.
|
||||
enableMapSet();
|
||||
|
||||
/**
|
||||
* Creates a standard model state and returns an array composed of the state
|
||||
* and an action dispatcher that can be used to change it. Since the conversion
|
||||
@@ -56,6 +60,7 @@ export const useStandardModel = (
|
||||
useImmerReducer(reduce, originalRole, initializeState);
|
||||
|
||||
const initializeState = (originalRole?: Role): StandardEditorModel => {
|
||||
const isEditing = !!originalRole;
|
||||
const role = originalRole ?? newRole();
|
||||
const roleModel = safelyConvertRoleToEditorModel(role);
|
||||
return {
|
||||
@@ -64,6 +69,14 @@ const initializeState = (originalRole?: Role): StandardEditorModel => {
|
||||
isDirty: !originalRole, // New role is dirty by default.
|
||||
validationResult:
|
||||
roleModel && validateRoleEditorModel(roleModel, undefined, undefined),
|
||||
currentTab: StandardEditorTab.Overview,
|
||||
disabledTabs: isEditing
|
||||
? new Set()
|
||||
: new Set([
|
||||
StandardEditorTab.Resources,
|
||||
StandardEditorTab.AdminRules,
|
||||
StandardEditorTab.Options,
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -82,6 +95,7 @@ const safelyConvertRoleToEditorModel = (
|
||||
export type StandardModelDispatcher = Dispatch<StandardModelAction>;
|
||||
|
||||
export enum ActionType {
|
||||
SetCurrentTab = 'SetCurrentTab',
|
||||
SetModel = 'SetModel',
|
||||
ResetToStandard = 'ResetToStandard',
|
||||
SetMetadata = 'SetMetadata',
|
||||
@@ -99,6 +113,7 @@ export enum ActionType {
|
||||
}
|
||||
|
||||
type StandardModelAction =
|
||||
| SetCurrentTabAction
|
||||
| SetModelAction
|
||||
| ResetToStandardAction
|
||||
| SetMetadataAction
|
||||
@@ -115,6 +130,10 @@ type StandardModelAction =
|
||||
| EnableValidationAction;
|
||||
|
||||
/** Sets the entire model. */
|
||||
type SetCurrentTabAction = {
|
||||
type: ActionType.SetCurrentTab;
|
||||
payload: StandardEditorTab;
|
||||
};
|
||||
type SetModelAction = { type: ActionType.SetModel; payload: RoleEditorModel };
|
||||
type ResetToStandardAction = {
|
||||
type: ActionType.ResetToStandard;
|
||||
@@ -175,6 +194,11 @@ const reduce = (
|
||||
// This reduce uses Immer, so we modify the model draft directly.
|
||||
// TODO(bl-nero): add immutability to the model data types.
|
||||
switch (type) {
|
||||
case ActionType.SetCurrentTab:
|
||||
state.currentTab = payload;
|
||||
state.disabledTabs.delete(payload);
|
||||
break;
|
||||
|
||||
case ActionType.SetModel:
|
||||
state.roleModel = payload;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user