mirror of https://github.com/harness/drone.git
feat: [CDE-251]: SSH Dropdown (#2576)
* feat: [CDE-251]: Updated harness code icon * feat: [CDE-251]: SSH Token creation modal * feat: [CDE-251]: SSH Dropdownpull/3545/head
parent
9ed0df9e92
commit
b83779350d
|
@ -73,6 +73,9 @@ export interface AppProps {
|
|||
useFeatureFlags: Unknown
|
||||
useGetSettingValue: Unknown
|
||||
useGetUserSourceCodeManagers?: Unknown
|
||||
useListAggregatedTokens?: Unknown
|
||||
useDeleteToken?: Unknown
|
||||
useCreateToken?: Unknown
|
||||
}>
|
||||
|
||||
currentUser: Required<TypesUser>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg width="32" height="33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.6 29.7c7.07 0 12.8-5.73 12.8-12.8S22.67 4.1 15.6 4.1c-2.029 0-3.947.472-5.652 1.312l3.473 3.28c.39-.269.859-.426 1.364-.426 1.35 0 2.444 1.12 2.444 2.5 0 .446-.114.864-.314 1.227l3.357 3.171a2.389 2.389 0 0 1 1.03-.232c1.35 0 2.443 1.12 2.443 2.5s-1.094 2.5-2.443 2.5c-1.35 0-2.444-1.12-2.444-2.5 0-.395.09-.768.249-1.1l-3.428-3.239c-.193.078-.396.13-.609.156l-1.816 8.744a2.512 2.512 0 0 1 1.124 2.104c0 1.38-1.094 2.5-2.444 2.5-1.35 0-2.443-1.12-2.443-2.5 0-1.288.953-2.349 2.177-2.485l1.813-8.732a2.512 2.512 0 0 1-1.14-2.114c0-.253.037-.496.105-.726L8.46 6.275A12.787 12.787 0 0 0 2.8 16.9c0 7.07 5.73 12.8 12.8 12.8Z" fill="url(#a)"/><defs><linearGradient id="a" x1="8.628" y1="6.165" x2="22.571" y2="27.635" gradientUnits="userSpaceOnUse"><stop stop-color="#FF7F77"/><stop offset="0" stop-color="#FD7A64"/><stop offset="1" stop-color="#E62D14"/></linearGradient></defs></svg>
|
||||
<svg width="563" height="563" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M25.74 219.359c-34.32 34.319-34.32 89.963 0 124.281l193.619 193.621c34.319 34.319 89.963 34.319 124.281 0L537.261 343.64c34.319-34.318 34.319-89.962 0-124.281L343.64 25.739c-34.318-34.319-89.962-34.319-124.281 0L25.739 219.36Zm166.753-8.686c6.864-6.864 6.864-17.994 0-24.857-6.864-6.864-17.994-6.864-24.857 0l-83.255 83.256c-6.864 6.864-6.864 17.992 0 24.856l83.255 83.256c6.863 6.864 17.993 6.864 24.857 0 6.864-6.865 6.864-17.993 0-24.857L121.665 281.5l70.828-70.827Zm174.954-24.857c-6.863 6.863-6.863 17.993 0 24.857l70.827 70.827-70.827 70.827c-6.863 6.864-6.863 17.992 0 24.857 6.864 6.864 17.992 6.864 24.856 0l83.256-83.256c6.864-6.864 6.864-17.992 0-24.856l-83.256-83.256c-6.864-6.864-17.992-6.864-24.856 0ZM330.17 169.77c2.667-9.334-2.737-19.061-12.07-21.728-9.335-2.667-19.062 2.737-21.729 12.072l-66.603 233.114c-2.667 9.334 2.738 19.061 12.072 21.728 9.333 2.667 19.06-2.737 21.727-12.07L330.17 169.77Z" fill="url(#a)"/><defs><linearGradient id="a" x1="128.184" y1="45.414" x2="434.816" y2="517.585" gradientUnits="userSpaceOnUse"><stop stop-color="#73DFE7"/><stop offset="1" stop-color="#0095F7"/></linearGradient></defs></svg>
|
Before Width: | Height: | Size: 1009 B After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.995 4.25a3.754 3.754 0 0 0-3.4 5.35.25.25 0 0 1-.05.284L4.25 14.123v1.627h1.481v-1.101a.25.25 0 0 1 .25-.25h1.076v-1.101a.25.25 0 0 1 .25-.25H8.43v-1.031a.25.25 0 0 1 .25-.25h1.079v-.285a.25.25 0 0 1 .374-.217 3.754 3.754 0 1 0 1.863-7.015Zm-1.736 7.64A4.254 4.254 0 1 0 8.07 9.65l-4.246 4.19a.25.25 0 0 0-.075.178V16c0 .138.112.25.25.25h1.981a.25.25 0 0 0 .25-.25v-1.101h1.076a.25.25 0 0 0 .25-.25v-1.101H8.68a.25.25 0 0 0 .25-.25v-1.031h1.079a.25.25 0 0 0 .25-.25v-.128Zm2.871-5.628a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-1.25.75a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0Z" fill="#6B6D85"/></svg>
|
After Width: | Height: | Size: 728 B |
|
@ -0,0 +1,26 @@
|
|||
.note {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
background: var(--blue-100) !important;
|
||||
padding: var(--spacing-small) 0 !important;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sshKeyValue {
|
||||
:global {
|
||||
textarea {
|
||||
height: 108px !important;
|
||||
resize: vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 35% !important;
|
||||
}
|
||||
|
||||
.addsshButton {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
font-size: var(--font-size-small) !important;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const addsshButton: string
|
||||
export declare const main: string
|
||||
export declare const note: string
|
||||
export declare const sshKeyValue: string
|
|
@ -0,0 +1,246 @@
|
|||
import React from 'react'
|
||||
import * as Yup from 'yup'
|
||||
import {
|
||||
Button,
|
||||
ButtonVariation,
|
||||
Container,
|
||||
Dialog,
|
||||
Formik,
|
||||
FormikForm,
|
||||
FormInput,
|
||||
Layout,
|
||||
Text,
|
||||
useToaster
|
||||
} from '@harnessio/uicore'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { Menu, MenuItem } from '@blueprintjs/core'
|
||||
import { Color } from '@harnessio/design-system'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import Secret from 'cde-gitness/assests/secret.svg?url'
|
||||
import { useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams'
|
||||
import type { OpenapiCreateGitspaceRequest } from 'services/cde'
|
||||
import { useModalHook } from 'hooks/useModalHook'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import { CDECustomDropdown } from '../CDECustomDropdown/CDECustomDropdown'
|
||||
import { getSelectedExpirationDate } from './CDESSHSelect.utils'
|
||||
import css from './CDESSHSelect.module.scss'
|
||||
|
||||
export const CDESSHSelect = () => {
|
||||
const { getString } = useStrings()
|
||||
const { showError } = useToaster()
|
||||
const { values, setFieldValue } = useFormikContext<OpenapiCreateGitspaceRequest>()
|
||||
const { accountIdentifier } = useGetCDEAPIParams()
|
||||
const { hooks, currentUser } = useAppContext()
|
||||
const { useListAggregatedTokens, useDeleteToken, useCreateToken } = hooks
|
||||
const { data, loading, refetch } = useListAggregatedTokens({
|
||||
queryParams: {
|
||||
accountIdentifier,
|
||||
apiKeyType: 'SSH_KEY'
|
||||
}
|
||||
})
|
||||
|
||||
const { mutate: createToken } = useCreateToken({
|
||||
queryParams: { accountIdentifier }
|
||||
})
|
||||
|
||||
const { mutate: deleteToken } = useDeleteToken({
|
||||
queryParams: {
|
||||
accountIdentifier,
|
||||
apiKeyType: 'SSH_KEY',
|
||||
parentIdentifier: currentUser.uid || 'cWQVVVCET4iwomT4hRoj4w',
|
||||
apiKeyIdentifier: `cdesshkey`
|
||||
}
|
||||
})
|
||||
|
||||
const tokenList = data?.data?.content.map((item: { token: any }) => item.token) || []
|
||||
|
||||
const [openModal, hideModal] = useModalHook(() => {
|
||||
const expiryOptions = [
|
||||
{ label: getString('cde.sshSelect.30days'), value: '30' },
|
||||
{ label: getString('cde.sshSelect.90days'), value: '90' },
|
||||
{ label: getString('cde.sshSelect.180days'), value: '180' },
|
||||
{ label: getString('cde.sshSelect.noexpiration'), value: '-1' }
|
||||
]
|
||||
|
||||
return (
|
||||
<Dialog isOpen onClose={hideModal} title="Add SSH Key" className={css.main}>
|
||||
<Layout.Vertical spacing="large">
|
||||
<Text margin="larg">Add an SSH key for secure access to Gitspaces via SSH.</Text>
|
||||
<Layout.Horizontal className={css.note} spacing="small">
|
||||
<Text icon="info-messaging" font="small" iconProps={{ size: 24 }}>
|
||||
SSH key are used to connect securely to workspaces
|
||||
</Text>
|
||||
<Text font="small" color={Color.PRIMARY_5}>
|
||||
Learn how to create an SSH Key
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
<Formik<{
|
||||
sshKeyName: string
|
||||
sshKeyValue: string
|
||||
expiryDate: string
|
||||
expiry: string
|
||||
}>
|
||||
formName="sshCreate"
|
||||
onSubmit={async value => {
|
||||
try {
|
||||
await createToken({
|
||||
identifier: value?.sshKeyName.trim(),
|
||||
name: value?.sshKeyName.trim(),
|
||||
description: '',
|
||||
tags: {},
|
||||
accountIdentifier,
|
||||
apiKeyIdentifier: `cdesshkey`,
|
||||
parentIdentifier: currentUser.uid || 'cWQVVVCET4iwomT4hRoj4w',
|
||||
apiKeyType: 'SSH_KEY',
|
||||
sshKeyContent: value.sshKeyValue,
|
||||
sshKeyUsage: ['AUTH'],
|
||||
validTo: Date.parse(value?.expiryDate),
|
||||
validFrom: new Date().getTime()
|
||||
})
|
||||
setFieldValue('ssh_token_identifier', value?.sshKeyName.trim())
|
||||
hideModal()
|
||||
refetch()
|
||||
} catch (error) {
|
||||
showError(getErrorMessage(error))
|
||||
hideModal()
|
||||
}
|
||||
}}
|
||||
initialValues={{
|
||||
sshKeyName: '',
|
||||
sshKeyValue: '',
|
||||
expiryDate: getSelectedExpirationDate('30'),
|
||||
expiry: '30'
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
sshKeyName: Yup.string().required(),
|
||||
sshKeyValue: Yup.string().required()
|
||||
})}>
|
||||
{formikProps => {
|
||||
return (
|
||||
<FormikForm>
|
||||
<FormInput.Text name="sshKeyName" label="Key Name" />
|
||||
<FormInput.DropDown
|
||||
items={expiryOptions}
|
||||
name="expiry"
|
||||
dropDownProps={{
|
||||
filterable: false,
|
||||
minWidth: 100
|
||||
}}
|
||||
label={getString('expirationDate')}
|
||||
onChange={item => {
|
||||
formikProps.setFieldValue('expiryDate', getSelectedExpirationDate(item.value.toString()))
|
||||
}}
|
||||
/>
|
||||
<FormInput.TextArea
|
||||
placeholder={`Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'`}
|
||||
name="sshKeyValue"
|
||||
label="SSH Key"
|
||||
isOptional
|
||||
className={css.sshKeyValue}
|
||||
/>
|
||||
<Layout.Horizontal spacing="large">
|
||||
<Button variation={ButtonVariation.PRIMARY} type="submit">
|
||||
Add Key
|
||||
</Button>
|
||||
<Button variation={ButtonVariation.TERTIARY}>Cancel</Button>
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
)
|
||||
}}
|
||||
</Formik>
|
||||
</Layout.Vertical>
|
||||
</Dialog>
|
||||
)
|
||||
}, [accountIdentifier])
|
||||
|
||||
const confirmDelete = useConfirmAct()
|
||||
|
||||
const handleDelete = async (e: React.MouseEvent<HTMLElement, MouseEvent>, tokenId: string): Promise<void> => {
|
||||
e.stopPropagation()
|
||||
confirmDelete({
|
||||
intent: 'danger',
|
||||
title: getString('cde.sshSelect.deleteToken', { name: tokenId }),
|
||||
message: getString('cde.deleteGitspaceText'),
|
||||
confirmText: getString('delete'),
|
||||
action: async () => {
|
||||
try {
|
||||
await deleteToken(tokenId || '', {
|
||||
headers: { 'content-type': 'application/json' }
|
||||
})
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(getErrorMessage(err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<CDECustomDropdown
|
||||
leftElement={
|
||||
<Layout.Horizontal>
|
||||
<img src={Secret} height={20} width={20} style={{ marginRight: '8px', alignItems: 'center' }} />
|
||||
<Layout.Vertical spacing="small">
|
||||
<Text>SSH Key </Text>
|
||||
<Text font="small" width="56%">
|
||||
By default we will create the SSH key used to login to the Gitspace. You can add keys under Preferences in
|
||||
User Settings
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Layout.Horizontal>
|
||||
}
|
||||
label={
|
||||
<Text icon={loading ? 'loading' : undefined}>{values?.ssh_token_identifier || '-- Select SSH Key --'}</Text>
|
||||
}
|
||||
menu={
|
||||
<Menu>
|
||||
<Container border={{ bottom: true }}>
|
||||
{tokenList.length ? (
|
||||
<>
|
||||
{tokenList.map((item: { name: string; identifier: string; apiKeyIdentifier: string }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.identifier}
|
||||
text={
|
||||
<Text
|
||||
width="100%"
|
||||
flex={{ justifyContent: 'space-between' }}
|
||||
rightIcon="cross"
|
||||
rightIconProps={{
|
||||
size: 12,
|
||||
onClick: e => {
|
||||
handleDelete(e, item.identifier)
|
||||
setFieldValue('ssh_token_identifier', undefined)
|
||||
}
|
||||
}}>
|
||||
{item.name}
|
||||
</Text>
|
||||
}
|
||||
onClick={() => {
|
||||
setFieldValue('ssh_token_identifier', item.identifier)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<Text padding="small">
|
||||
There are no keys configured. By default we will create a SSH key to login into Gitspace.
|
||||
</Text>
|
||||
)}
|
||||
</Container>
|
||||
<Button
|
||||
variation={ButtonVariation.LINK}
|
||||
onClick={() => {
|
||||
openModal()
|
||||
}}
|
||||
className={css.addsshButton}>
|
||||
Add SSH Key
|
||||
</Button>
|
||||
</Menu>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import moment from 'moment'
|
||||
|
||||
export const DATE_FORMAT = 'MM/DD/YYYY'
|
||||
|
||||
export const getReadableDateTime = (timestamp?: number, formatString = 'MMM DD, YYYY'): string => {
|
||||
if (!timestamp) {
|
||||
return ''
|
||||
}
|
||||
return moment(timestamp).format(formatString)
|
||||
}
|
||||
|
||||
export const getSelectedExpirationDate = (value: string): string => {
|
||||
switch (value) {
|
||||
case '30':
|
||||
case '90':
|
||||
case '180':
|
||||
return getReadableDateTime(new Date().setDate(new Date().getDate() + parseInt(value)), DATE_FORMAT)
|
||||
case '-1':
|
||||
return '12/31/2099'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
|
@ -13,8 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Button, ButtonVariation, Container, Formik, FormikForm, Layout, Text, useToaster } from '@harnessio/uicore'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
|
@ -37,6 +36,8 @@ import { OpenapiCreateGitspaceRequest, useCreateGitspace } from 'services/cde'
|
|||
import { useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams'
|
||||
import { GitnessRepoImportForm } from 'cde-gitness/components/GitnessRepoImportForm/GitnessRepoImportForm'
|
||||
import { EnumGitspaceCodeRepoType } from 'cde-gitness/constants'
|
||||
import { CDESSHSelect } from 'cde-gitness/components/CDESSHSelect/CDESSHSelect'
|
||||
import { useQueryParams } from 'hooks/useQueryParams'
|
||||
import { gitnessFormInitialValues } from './GitspaceCreate.constants'
|
||||
import { validateGitnessForm } from './GitspaceCreate.utils'
|
||||
import css from './GitspaceCreate.module.scss'
|
||||
|
@ -56,11 +57,14 @@ export const CDECreateGitspace = () => {
|
|||
const { accountIdentifier = '', orgIdentifier = '', projectIdentifier = '' } = useGetCDEAPIParams()
|
||||
const { showSuccess, showError } = useToaster()
|
||||
const { mutate } = useCreateGitspace({ accountIdentifier, orgIdentifier, projectIdentifier })
|
||||
const { idpRepoURL = '' } = useQueryParams<{ idpRepoURL?: string }>()
|
||||
|
||||
const [repoURLviaQueryParam, setrepoURLviaQueryParam] = useState('')
|
||||
|
||||
const scmOptions: SCMType[] = [
|
||||
{ name: 'Harness Code', value: EnumGitspaceCodeRepoType.HARNESS_CODE, icon: harnessCode },
|
||||
{ name: 'Github', value: EnumGitspaceCodeRepoType.GITHUB, icon: github },
|
||||
{ name: 'Gitlab', value: EnumGitspaceCodeRepoType.GITLAB, icon: gitlab },
|
||||
{ name: 'GitHub Cloud', value: EnumGitspaceCodeRepoType.GITHUB, icon: github },
|
||||
{ name: 'GitLab Cloud', value: EnumGitspaceCodeRepoType.GITLAB, icon: gitlab },
|
||||
{ name: 'Bitbucket', value: EnumGitspaceCodeRepoType.BITBUCKET, icon: bitbucket },
|
||||
{ name: 'Any public Git repository', value: EnumGitspaceCodeRepoType.UNKNOWN, icon: genericGit }
|
||||
]
|
||||
|
@ -69,6 +73,12 @@ export const CDECreateGitspace = () => {
|
|||
queryParams: { accountIdentifier, userIdentifier: currentUser?.uid }
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (idpRepoURL !== repoURLviaQueryParam) {
|
||||
setrepoURLviaQueryParam(idpRepoURL)
|
||||
}
|
||||
}, [idpRepoURL])
|
||||
|
||||
const oauthSCMsListTypes =
|
||||
OauthSCMs?.data?.userSourceCodeManagerResponseDTOList?.map((item: { type: string }) => item.type.toLowerCase()) ||
|
||||
[]
|
||||
|
@ -96,7 +106,11 @@ export const CDECreateGitspace = () => {
|
|||
showError(getErrorMessage(error))
|
||||
}
|
||||
}}
|
||||
initialValues={{ ...gitnessFormInitialValues, code_repo_type: EnumGitspaceCodeRepoType.HARNESS_CODE }}
|
||||
initialValues={{
|
||||
...gitnessFormInitialValues,
|
||||
code_repo_url: idpRepoURL,
|
||||
code_repo_type: EnumGitspaceCodeRepoType.HARNESS_CODE
|
||||
}}
|
||||
validationSchema={validateGitnessForm(getString)}
|
||||
formName="importRepoForm"
|
||||
enableReinitialize>
|
||||
|
@ -204,6 +218,7 @@ export const CDECreateGitspace = () => {
|
|||
</Container>
|
||||
<Container className={css.formOuterContainer}>
|
||||
<CDEIDESelect onChange={formik.setFieldValue} selectedIde={formik.values.ide} />
|
||||
<CDESSHSelect />
|
||||
<SelectInfraProvider />
|
||||
<Button width={'100%'} variation={ButtonVariation.PRIMARY} height={50} type="submit">
|
||||
{getString('cde.createGitspace')}
|
||||
|
|
|
@ -262,6 +262,11 @@ export interface StringsMap {
|
|||
'cde.repositoryAndBranch': string
|
||||
'cde.retry': string
|
||||
'cde.sessionDuration': string
|
||||
'cde.sshSelect.180days': string
|
||||
'cde.sshSelect.30days': string
|
||||
'cde.sshSelect.90days': string
|
||||
'cde.sshSelect.deleteToken': string
|
||||
'cde.sshSelect.noexpiration': string
|
||||
'cde.startingGitspace': string
|
||||
'cde.status': string
|
||||
'cde.stopingGitspace': string
|
||||
|
|
|
@ -1152,7 +1152,7 @@ cde:
|
|||
na: NA
|
||||
retry: Retry
|
||||
eventTimeline: Event Timeline
|
||||
cpu: Cpu
|
||||
cpu: CPU
|
||||
memory: Memory
|
||||
disk: Disk
|
||||
regionSelectWarning: Select a region first
|
||||
|
@ -1173,6 +1173,12 @@ cde:
|
|||
status: Status
|
||||
getStarted: Get started with Gitspaces by
|
||||
createImport: creating or importing repositories
|
||||
sshSelect:
|
||||
30days: 30 Days
|
||||
90days: 90 Days
|
||||
180days: 180 Days
|
||||
noexpiration: No Expiration
|
||||
deleteToken: Delete SSH Token '{{name}}'
|
||||
homePage:
|
||||
start: Start
|
||||
noSetupRequired: no setup required
|
||||
|
|
Loading…
Reference in New Issue