Skip to content

Commit

Permalink
Add the options panel to role editor (#49801) (#49984)
Browse files Browse the repository at this point in the history
* Add admin rule tab to the role editor

Also tightens some constraints about standard role editor conformance.

* Add a way to delete an admin rule

* Add the options panel to role editor

* Review

* Review
  • Loading branch information
bl-nero authored Dec 10, 2024
1 parent 290e49c commit e30a61d
Show file tree
Hide file tree
Showing 5 changed files with 481 additions and 11 deletions.
2 changes: 1 addition & 1 deletion web/packages/design/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import CardSuccess, { CardSuccessLogin } from './CardSuccess';
import { Indicator } from './Indicator';
import Input from './Input';
import Label from './Label';
import LabelInput from './LabelInput';
import { LabelInput } from './LabelInput';
import LabelState from './LabelState';
import Link from './Link';
import { Mark } from './Mark';
Expand Down
191 changes: 185 additions & 6 deletions web/packages/teleport/src/Roles/RoleEditor/StandardEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
Flex,
H3,
H4,
Input,
LabelInput,
Mark,
Text,
} from 'design';
Expand All @@ -43,6 +45,10 @@ import {
} from 'shared/components/FieldSelect';
import { SlideTabs } from 'design/SlideTabs';

import { RadioGroup } from 'design/RadioGroup';

import Select from 'shared/components/Select';

import { Role, RoleWithYaml } from 'teleport/services/resources';
import { LabelsInput } from 'teleport/components/LabelsInput';

Expand Down Expand Up @@ -70,6 +76,9 @@ import {
resourceKindOptions,
verbOptions,
newRuleModel,
OptionsModel,
requireMFATypeOptions,
createHostUserModeOptions,
} from './standardmodel';
import {
validateRoleEditorModel,
Expand Down Expand Up @@ -199,6 +208,13 @@ export const StandardEditor = ({
});
}

function setOptions(options: OptionsModel) {
handleChange({
...standardEditorModel,
options,
});
}

return (
<>
{roleModel.requiresReset && (
Expand Down Expand Up @@ -252,7 +268,7 @@ export const StandardEditor = ({
onChange={setCurrentTab}
/>
</Box>
<div
<Box
id={overviewTabId}
style={{
display: currentTab === StandardEditorTab.Overview ? '' : 'none',
Expand All @@ -264,8 +280,8 @@ export const StandardEditor = ({
validation={validation.metadata}
onChange={metadata => handleChange({ ...roleModel, metadata })}
/>
</div>
<div
</Box>
<Box
id={resourcesTabId}
style={{
display: currentTab === StandardEditorTab.Resources ? '' : 'none',
Expand Down Expand Up @@ -317,8 +333,8 @@ export const StandardEditor = ({
</MenuButton>
</Box>
</Flex>
</div>
<div
</Box>
<Box
id={accessRulesTabId}
style={{
display: currentTab === StandardEditorTab.AccessRules ? '' : 'none',
Expand All @@ -330,7 +346,19 @@ export const StandardEditor = ({
onChange={setRules}
validation={validation.rules}
/>
</div>
</Box>
<Box
id={optionsTabId}
style={{
display: currentTab === StandardEditorTab.Options ? '' : 'none',
}}
>
<Options
isProcessing={isProcessing}
value={roleModel.options}
onChange={setOptions}
/>
</Box>
</EditorWrapper>
<EditorSaveCancelButton
onSave={() => handleSave()}
Expand Down Expand Up @@ -996,6 +1024,157 @@ function AccessRule({
);
}

function Options({
value,
isProcessing,
onChange,
}: SectionProps<OptionsModel, never>) {
const theme = useTheme();
const id = useId();
const maxSessionTTLId = `${id}-max-session-ttl`;
const clientIdleTimeoutId = `${id}-client-idle-timeout`;
const requireMFATypeId = `${id}-require-mfa-type`;
const createHostUserModeId = `${id}-create-host-user-mode`;
return (
<OptionsGridContainer
border={1}
borderColor={theme.colors.interactive.tonal.neutral[0]}
borderRadius={3}
p={3}
>
<OptionsHeader>Global Settings</OptionsHeader>

<OptionLabel htmlFor={maxSessionTTLId}>Max Session TTL</OptionLabel>
<Input
id={maxSessionTTLId}
value={value.maxSessionTTL}
disabled={isProcessing}
onChange={e => onChange({ ...value, maxSessionTTL: e.target.value })}
/>

<OptionLabel htmlFor={clientIdleTimeoutId}>
Client Idle Timeout
</OptionLabel>
<Input
id={clientIdleTimeoutId}
value={value.clientIdleTimeout}
disabled={isProcessing}
onChange={e =>
onChange({ ...value, clientIdleTimeout: e.target.value })
}
/>

<Box>Disconnect When Certificate Expires</Box>
<BoolRadioGroup
name="disconnect-expired-cert"
value={value.disconnectExpiredCert}
onChange={d => onChange({ ...value, disconnectExpiredCert: d })}
/>

<OptionLabel htmlFor={requireMFATypeId}>Require Session MFA</OptionLabel>
<Select
inputId={requireMFATypeId}
isDisabled={isProcessing}
options={requireMFATypeOptions}
value={value.requireMFAType}
onChange={t => onChange?.({ ...value, requireMFAType: t })}
/>

<OptionsHeader separator>SSH</OptionsHeader>

<OptionLabel htmlFor={createHostUserModeId}>
Create Host User Mode
</OptionLabel>
<Select
inputId={createHostUserModeId}
isDisabled={isProcessing}
options={createHostUserModeOptions}
value={value.createHostUserMode}
onChange={m => onChange?.({ ...value, createHostUserMode: m })}
/>

<OptionsHeader separator>Database</OptionsHeader>

<Box>Create Database User</Box>
<BoolRadioGroup
name="create-db-user"
value={value.createDBUser}
onChange={c => onChange({ ...value, createDBUser: c })}
/>

{/* TODO(bl-nero): a bug in YAML unmarshalling backend breaks the
createDBUserMode field. Fix it and add the field here. */}

<OptionsHeader separator>Desktop</OptionsHeader>

<Box>Create Desktop User</Box>
<BoolRadioGroup
name="create-desktop-user"
value={value.createDesktopUser}
onChange={c => onChange({ ...value, createDesktopUser: c })}
/>

<Box>Allow Clipboard Sharing</Box>
<BoolRadioGroup
name="desktop-clipboard"
value={value.desktopClipboard}
onChange={c => onChange({ ...value, desktopClipboard: c })}
/>

<Box>Allow Directory Sharing</Box>
<BoolRadioGroup
name="desktop-directory-sharing"
value={value.desktopDirectorySharing}
onChange={s => onChange({ ...value, desktopDirectorySharing: s })}
/>
</OptionsGridContainer>
);
}

const OptionsGridContainer = styled(Box)`
display: grid;
grid-template-columns: 1fr 1fr;
align-items: baseline;
row-gap: ${props => props.theme.space[3]}px;
`;

const OptionsHeader = styled(H4)<{ separator?: boolean }>`
grid-column: 1/3;
border-top: ${props =>
props.separator
? `${props.theme.borders[1]} ${props.theme.colors.interactive.tonal.neutral[0]}`
: 'none'};
padding-top: ${props =>
props.separator ? `${props.theme.space[3]}px` : '0'};
`;

function BoolRadioGroup({
name,
value,
onChange,
}: {
name: string;
value: boolean;
onChange(b: boolean): void;
}) {
return (
<RadioGroup
name={name}
flexDirection="row"
options={[
{ label: 'True', value: 'true' },
{ label: 'False', value: 'false' },
]}
value={String(value)}
onChange={d => onChange(d === 'true')}
/>
);
}

const OptionLabel = styled(LabelInput)`
${props => props.theme.typography.body2}
`;

export const EditorWrapper = styled(Box)<{ mute?: boolean }>`
opacity: ${p => (p.mute ? 0.4 : 1)};
pointer-events: ${p => (p.mute ? 'none' : '')};
Expand Down
Loading

0 comments on commit e30a61d

Please sign in to comment.