Add token create forms to Join Tokens UI#44408
Conversation
kimlisa
left a comment
There was a problem hiding this comment.
some other UX issues:
- when the dialog is open (viewing a token), if i press the create button or try viewing other tokens, my current opened dialog does not update the state (btw you can trigger a component re-mount by defining a
keyattribute) - when i delete the token that i am viewing, it does not close the dialog
unless i'm missing something, i can't access the yaml editor until after I am editing something 🤔 |
|
We don't allow a YAML editor to create at all and push towards creating only IAM or GCP. So you are correct that you will only see it while editing |
|
had to rebase, sorry. new commit here 3aa99c9 |
| tokenId := r.Header.Get(HeaderTokenName) | ||
| if tokenId == "" { | ||
| return nil, trace.BadParameter("requires a token name to edit") | ||
| } |
There was a problem hiding this comment.
Does this make the endpoint not backwards compatible? Should this use a new endpoint instead?
There was a problem hiding this comment.
These are new endpoints (I'm editing this one but it isn't actually used/released yet, just from my last PR) so no worry about backward compatibility here.
| h.POST("/webapi/token", h.WithAuth(h.createTokenHandle)) | ||
| h.PUT("/webapi/token/yaml", h.WithAuth(h.editTokenYAML)) | ||
| // used for creating a new token | ||
| h.POST("/webapi/token/new", h.WithAuth(h.upsertTokenHandle)) |
There was a problem hiding this comment.
Should we have /new in the URL? I don't see any other endpoint that uses this convention
There was a problem hiding this comment.
I can think of something better than /new, sure
There was a problem hiding this comment.
i just made it plural "/webapi/tokens", as posting to a REST api should end in the plural of the resource anyway.
| AllowRules []string `json:"allowRules,omitempty"` | ||
| // Allow is a list of allow rules | ||
| Allow []*types.TokenRule `json:"allow,omitempty"` | ||
| GCP *types.ProvisionTokenSpecV2GCP `json:"gcp,omitempty"` |
| const joinRoleOptions: Option<JoinRole, JoinRole>[] = [ | ||
| 'App', | ||
| 'Node', | ||
| 'Db', | ||
| 'Kube', | ||
| 'Bot', | ||
| 'WindowsDesktop', | ||
| 'Discovery', | ||
| ].map(role => ({ value: role as JoinRole, label: role as JoinRole })); | ||
|
|
||
| const availableJoinMethods: Option<JoinMethod, JoinMethod>[] = [ | ||
| 'iam', | ||
| 'gcp', | ||
| ].map(method => ({ | ||
| value: method as JoinMethod, | ||
| label: method as JoinMethod, | ||
| })); |
There was a problem hiding this comment.
Should the labels for these options be something a bit more user friendly?
There was a problem hiding this comment.
I chose this to match what's in the documentation and YAML for these join methods. I think in this case particularly this would be the most friendly/understood.
| </ButtonIcon> | ||
| </HoverTooltip> | ||
| <Text typography="h3" fontWeight={400}> | ||
| {editToken ? `Edit Token` : 'Create a New Join Token'} |
There was a problem hiding this comment.
Here the button would be "Create a New Join Token" and elsewhere there's a button that's "Create new token" - we should be consistent on capitalisation
| editTokenWithYAML(editToken.id); | ||
| }} | ||
| > | ||
| <Text color="buttons.link.default">Use YAML editor instead</Text> |
There was a problem hiding this comment.
Maybe "instead" isn't needed here
There was a problem hiding this comment.
I wanted to imply "one or the other" here but, i'll just remove "instead" and see if any user complains. thank you!
| cluster. This will remove the ability for any new resources to join | ||
| with this token and any non-renewable resource from renewing. | ||
| cluster. This will remove the ability for any new resources to join or | ||
| renew with this token and any non-renewable resource from renewing. |
There was a problem hiding this comment.
"to join or renew with this token" - the "or renew with this token" part sounds a bit weird to me
There was a problem hiding this comment.
I'll remove as "renew" is kind of a join anyway
| setTokenState, | ||
| }: { | ||
| rules: NewJoinTokenGCPState[]; | ||
| setTokenState: React.Dispatch<React.SetStateAction<NewJoinTokenState>>; |
There was a problem hiding this comment.
I've never really been a fan of passing the React.Dispatch method to child components - I think components should care about themselves and offer a onXXX prop, then the parent component updates its own state instead of passing down the setState method
There was a problem hiding this comment.
Great point, will change!
| padding-top: ${props => props.theme.space[3]}px; | ||
| padding-bottom: ${props => props.theme.space[3]}px; | ||
| padding-left: ${props => props.theme.space[3]}px; | ||
| padding-right: ${props => props.theme.space[3]}px; |
There was a problem hiding this comment.
| padding-top: ${props => props.theme.space[3]}px; | |
| padding-bottom: ${props => props.theme.space[3]}px; | |
| padding-left: ${props => props.theme.space[3]}px; | |
| padding-right: ${props => props.theme.space[3]}px; | |
| padding: ${props => props.theme.space[3]}px; |
| // used for creating a new token | ||
| h.POST("/webapi/tokens", h.WithAuth(h.upsertTokenHandle)) | ||
| // used for updating a token | ||
| h.PUT("/webapi/token", h.WithAuth(h.upsertTokenHandle)) |
There was a problem hiding this comment.
Normally a REST API would be something like PUT /webapi/token/name for editing, and not rely on an arbitrary header for specifying the token name - thoughts?
There was a problem hiding this comment.
We've chosen to do the header for tokens to obfuscate a potentially secret token name from request logs
| const newRules = [...rules].filter((rule, i) => { | ||
| if (index === i) { | ||
| return false; | ||
| } | ||
| return rule; | ||
| }); |
There was a problem hiding this comment.
| const newRules = [...rules].filter((rule, i) => { | |
| if (index === i) { | |
| return false; | |
| } | |
| return rule; | |
| }); | |
| const newRules = rules.filter((rule, i) => index !== i); |
filter expects a boolean so we should be returning true or false, and .filter creates a new copy of the array so there's no need for [...rules]
There was a problem hiding this comment.
Ah I think this was originally a map and then a filter and then map and then ended up with this monstrosity. Thanks for the catch
| ))} | ||
| <ButtonText onClick={addNewRule}> | ||
| <Plus size={16} mr={2} /> | ||
| Add another AWS Account |
There was a problem hiding this comment.
Should this be "Add another AWS rule" or something?
| const newRules = [...rules].filter((rule, i) => { | ||
| if (index === i) { | ||
| return false; | ||
| } | ||
| return rule; | ||
| }); |
| updateRuleField(index, 'locations', opts as OptionGCP[]) | ||
| } | ||
| value={rule.locations} | ||
| label="Add Locatons" |
There was a problem hiding this comment.
| label="Add Locatons" | |
| label="Add Locations" |
| {index > 0 && ( // at least one rule is required, so lets not allow the user to remove it | ||
| <ButtonIcon onClick={() => removeRule(index)}> | ||
| <Trash size={16} color="text.muted" /> | ||
| </ButtonIcon> | ||
| )} |
There was a problem hiding this comment.
Same comment about rules.length > 1
| > | ||
| <Flex alignItems="center" justifyContent="space-between"> | ||
| <Text fontWeight={700} mb={2}> | ||
| AWS Account |
| <FieldInput | ||
| label="Account ID" | ||
| rule={requiredField('Account ID is required')} | ||
| placeholder="aws account ID" |
There was a problem hiding this comment.
| placeholder="aws account ID" | |
| placeholder="AWS Account ID" |
| 'Discovery', | ||
| ].map(role => ({ value: role as JoinRole, label: role as JoinRole })); | ||
|
|
||
| const availableJoinMethods: Option<JoinMethod, JoinMethod>[] = [ |
There was a problem hiding this comment.
As these are typed as OptionJoinMethod and OptionJoinRole below, this should probably use those types
| } | ||
| value={rule.project_ids} | ||
| label="Add Project ID(s)" | ||
| rule={requiredField('At least 1 projectID required')} |
There was a problem hiding this comment.
| rule={requiredField('At least 1 projectID required')} | |
| rule={requiredField('At least 1 project ID required')} |
|
I will backport this in a group with the rest of the previous supporting PRs |
kimlisa
left a comment
There was a problem hiding this comment.
overall lgtm, there is one change that will make creating integrations break that needs to be addressed
| h.PUT("/webapi/token/yaml", h.WithAuth(h.editTokenYAML)) | ||
| // used for creating a new token | ||
| h.POST("/webapi/tokens", h.WithAuth(h.upsertTokenHandle)) | ||
| // used for updating a token | ||
| h.PUT("/webapi/token", h.WithAuth(h.upsertTokenHandle)) |
There was a problem hiding this comment.
should all these be /webapi/tokens (plura)?
| // join token handlers | ||
| h.PUT("/webapi/token/yaml", h.WithAuth(h.upsertTokenContent)) | ||
| h.POST("/webapi/token", h.WithAuth(h.createTokenHandle)) | ||
| h.PUT("/webapi/token/yaml", h.WithAuth(h.editTokenYAML)) |
There was a problem hiding this comment.
nit. the UI tends use the word edit but backend uses update (afaik)
| h.PUT("/webapi/token/yaml", h.WithAuth(h.editTokenYAML)) | |
| h.PUT("/webapi/token/yaml", h.WithAuth(h.updateTokenYAML)) |
| } | ||
|
|
||
| if tokenId != extractedRes.Metadata.Name { | ||
| return nil, trace.BadParameter("renaming tokens is not supported.") |
There was a problem hiding this comment.
can't remember... i think error messaging only has proper period stuff if the msg starts with a capital
| return nil, trace.BadParameter("renaming tokens is not supported.") | |
| return nil, trace.BadParameter("renaming tokens is not supported") |
| } | ||
|
|
||
| if editing && tokenId != req.Name { | ||
| return nil, trace.BadParameter("renaming tokens is not supported.") |
There was a problem hiding this comment.
| return nil, trace.BadParameter("renaming tokens is not supported.") | |
| return nil, trace.BadParameter("renaming tokens is not supported") |
| expires := time.Now().UTC().Add(defaults.NodeJoinTokenTTL) | ||
| // IAM and GCP tokens should never expire | ||
| if req.JoinMethod == types.JoinMethodGCP || req.JoinMethod == types.JoinMethodIAM { | ||
| expires = time.Now().UTC().AddDate(1000, 0, 0) |
There was a problem hiding this comment.
is there a const for 1000? to consistently set a "never expires"
There was a problem hiding this comment.
We could add a constant but the time package doesn't have Year (at least my editor doesn't seem to think so) so it'd end up being only a const of 1000 which might not get across that its a "time" so probably best to leave out
|
|
||
| async function handleSave(content: string): Promise<void> { | ||
| const token = await ctx.joinTokenService.upsertJoinToken({ content }); | ||
| function updateTokenList(token: JoinToken): JoinToken[] { |
There was a problem hiding this comment.
does line 97 still apply? (since we prevent user from changing the name on edit now)
| labelTip="Allows regions and/or zones." | ||
| /> | ||
| <FieldSelectCreatable | ||
| placeholder="PROJECT_NUMBER_compute@developer.gserviceaccount.com" |
There was a problem hiding this comment.
maybe a shorter email example? b/c i can't see the entire text in the input box
There was a problem hiding this comment.
I think its in a specific format according to the docs, but i'll remove _NUMBER and that'll shorten it enough i think
| padding: ${props => props.theme.space[3]}px; | ||
| `; | ||
|
|
||
| const JoinTokenIAMForm = ({ |
There was a problem hiding this comment.
nit: i would pull these forms (JoinTokenIAMForm and JoinTokenGCPForm) out into its own file?
| /> | ||
| <FieldInput | ||
| label="ARN" | ||
| toolTipContent={`The joining nodes must match this ARN. Supports wildcards "*" and "?"`} |
There was a problem hiding this comment.
does this need to be a specific role arn format? can we maybe provide example? like how do you use the question mark in the arn?
eg (this is IAM role arn format)
- arn:aws:iam::account-id:role/role-name
- arn:aws:iam::account-id:role/*
- question mark?
There was a problem hiding this comment.
rename to UpsertXXX maybe? since it does both create and edit
| )} | ||
| </Flex> | ||
| <FieldSelectCreatable | ||
| placeholder="Type a project ID" |
There was a problem hiding this comment.
We should be consistent on capitalisation (project ID vs Project ID in the required rule)
|
I'll add more to the sorry now that the storybook was updated and some tests |
ryanclark
left a comment
There was a problem hiding this comment.
LGTM. might be worth adding some more tests in the future around the rule configuration
true, added some more around adding/deleting rules and also updating select join roles |
This adds the create tokens forms to our Join Tokens UI. The forms only support IAM, GCP and tokens for now (preferred) and allow a yaml editor for other types. You can also edit IAM and GCP and "token" in yaml if you wish but its purposefully kinda "hidden".
This adds the feature to the navigation under "Join Tokens".
closes #31671