Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v17] Add the options panel to role editor #49984

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading