mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* ServiceAccounts: refactor ServiceAccountRoleRow * Refactor ServiceAccountRoleRow * Refactor ServiceAccountProfile * Refactor components * Change service accounts icon * Refine service accounts page header * Improve service accounts filtering * Change delete button style * Tweak account id * Auto focus name field when create service account * Add disable/enable button * Enable/disable service accounts * Optimize updating service account (do not fetch all) * Remove status column (replace by enable/disable button) * Add banner with service accounts description * Add tokens from main page * Update tokens count when add token from main page * Fix action buttons column * Fix tokens count when change role * Refine table row classes * Fix buttons * Simplify working with state * Show message when service account updated * Able to filter disabled accounts * Mark disabled accounts in a table * Refine disabled account view * Move non-critical components to separate folder * Remove confusing focusing * Fix date picker position when creating new token * DatePicker: able to set minimum date that can be selected * Don't allow to select expiration dates prior today * Set tomorrow as a default token expiration date * Fix displaying expiration period * Rename Add token button * Refine page styles * Show modal when disabling SA from main page * Arrange role picker * Refine SA page styles * Generate default token name * More smooth navigation between SA pages * Stop loading indicator in case of error * Remove legacy styles usage * Tweaks after code review Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Get rid of useDisapatch in favor of mapDispatchToProps * Tests for ServiceAccountsListPage * Tests for service account page * Show new role picker only with license * Get rid of deprecated css classes * Apply suggestion from code review Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Fix service accounts icon * Tests for service account create page * Return service account info when update * Add behaviour tests for ServiceAccountsListPage * Fix disabled cursor on confirm button * More behavior tests for service account page * Temporary disable service account migration banner * Use safe where condition Co-authored-by: Jguer <joao.guerreiro@grafana.com> * Apply review suggestions Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Remove autofocus from search Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Jguer <joao.guerreiro@grafana.com>
214 lines
6.1 KiB
TypeScript
214 lines
6.1 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import React, { useEffect, useState } from 'react';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import {
|
|
Button,
|
|
ClipboardButton,
|
|
DatePickerWithInput,
|
|
Field,
|
|
FieldSet,
|
|
HorizontalGroup,
|
|
Icon,
|
|
Input,
|
|
Label,
|
|
Modal,
|
|
RadioButtonGroup,
|
|
useStyles2,
|
|
} from '@grafana/ui';
|
|
|
|
const EXPIRATION_OPTIONS = [
|
|
{ label: 'No expiration', value: false },
|
|
{ label: 'Set expiration date', value: true },
|
|
];
|
|
|
|
export type ServiceAccountToken = {
|
|
name: string;
|
|
secondsToLive?: number;
|
|
};
|
|
|
|
interface Props {
|
|
isOpen: boolean;
|
|
token: string;
|
|
serviceAccountLogin: string;
|
|
onCreateToken: (token: ServiceAccountToken) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateToken, onClose }: Props) => {
|
|
let tomorrow = new Date();
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
const [defaultTokenName, setDefaultTokenName] = useState('');
|
|
const [newTokenName, setNewTokenName] = useState('');
|
|
const [isWithExpirationDate, setIsWithExpirationDate] = useState(false);
|
|
const [newTokenExpirationDate, setNewTokenExpirationDate] = useState<Date | string>(tomorrow);
|
|
const [isExpirationDateValid, setIsExpirationDateValid] = useState(newTokenExpirationDate !== '');
|
|
const styles = useStyles2(getStyles);
|
|
|
|
useEffect(() => {
|
|
// Generate new token name every time we open modal
|
|
if (isOpen) {
|
|
setDefaultTokenName(`${serviceAccountLogin}-${uuidv4()}`);
|
|
}
|
|
}, [serviceAccountLogin, isOpen]);
|
|
|
|
const onExpirationDateChange = (value: Date | string) => {
|
|
const isValid = value !== '';
|
|
setIsExpirationDateValid(isValid);
|
|
setNewTokenExpirationDate(value);
|
|
};
|
|
|
|
const onGenerateToken = () => {
|
|
onCreateToken({
|
|
name: newTokenName || defaultTokenName,
|
|
secondsToLive: isWithExpirationDate ? getSecondsToLive(newTokenExpirationDate) : undefined,
|
|
});
|
|
};
|
|
|
|
const onCloseInternal = () => {
|
|
setNewTokenName('');
|
|
setDefaultTokenName('');
|
|
setIsWithExpirationDate(false);
|
|
setNewTokenExpirationDate(tomorrow);
|
|
setIsExpirationDateValid(newTokenExpirationDate !== '');
|
|
onClose();
|
|
};
|
|
|
|
const modalTitle = (
|
|
<div className={styles.modalHeaderTitle}>
|
|
<Icon className={styles.modalHeaderIcon} name="key-skeleton-alt" size="lg" />
|
|
<span>{!token ? 'Add service account token' : 'Service account token created'}</span>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<Modal
|
|
isOpen={isOpen}
|
|
title={modalTitle}
|
|
onDismiss={onCloseInternal}
|
|
className={styles.modal}
|
|
contentClassName={styles.modalContent}
|
|
>
|
|
{!token ? (
|
|
<div>
|
|
<FieldSet>
|
|
<Field
|
|
label="Display name"
|
|
description="Name to easily identify the token"
|
|
className={styles.modalRow}
|
|
// for now this is required
|
|
// need to make this optional in backend as well
|
|
required={true}
|
|
>
|
|
<Input
|
|
name="tokenName"
|
|
value={newTokenName}
|
|
placeholder={defaultTokenName}
|
|
onChange={(e) => {
|
|
setNewTokenName(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
</Field>
|
|
<RadioButtonGroup
|
|
className={styles.modalRow}
|
|
options={EXPIRATION_OPTIONS}
|
|
value={isWithExpirationDate}
|
|
onChange={setIsWithExpirationDate}
|
|
size="md"
|
|
/>
|
|
{isWithExpirationDate && (
|
|
<Field label="Expiration date" className={styles.modalRow}>
|
|
<DatePickerWithInput
|
|
onChange={onExpirationDateChange}
|
|
value={newTokenExpirationDate}
|
|
placeholder=""
|
|
minDate={tomorrow}
|
|
/>
|
|
</Field>
|
|
)}
|
|
</FieldSet>
|
|
<Button onClick={onGenerateToken} disabled={isWithExpirationDate && !isExpirationDateValid}>
|
|
Generate token
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<FieldSet>
|
|
<Label
|
|
description="You will not be able to see or generate it again. Loosing a token requires creating new one."
|
|
className={styles.modalRow}
|
|
>
|
|
Copy the token. It will be showed only once.
|
|
</Label>
|
|
<Field label="Token" className={styles.modalRow}>
|
|
<div className={styles.modalTokenRow}>
|
|
<Input name="tokenValue" value={token} readOnly />
|
|
<ClipboardButton
|
|
className={styles.modalCopyToClipboardButton}
|
|
variant="secondary"
|
|
size="md"
|
|
getText={() => token}
|
|
>
|
|
<Icon name="copy" /> Copy to clipboard
|
|
</ClipboardButton>
|
|
</div>
|
|
</Field>
|
|
</FieldSet>
|
|
<HorizontalGroup>
|
|
<ClipboardButton variant="primary" getText={() => token} onClipboardCopy={onCloseInternal}>
|
|
Copy to clipboard and close
|
|
</ClipboardButton>
|
|
<Button variant="secondary" onClick={onCloseInternal}>
|
|
Close
|
|
</Button>
|
|
</HorizontalGroup>
|
|
</>
|
|
)}
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
const getSecondsToLive = (date: Date | string) => {
|
|
const dateAsDate = new Date(date);
|
|
const now = new Date();
|
|
|
|
return Math.ceil((dateAsDate.getTime() - now.getTime()) / 1000);
|
|
};
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => {
|
|
return {
|
|
modal: css`
|
|
width: 550px;
|
|
`,
|
|
modalContent: css`
|
|
overflow: visible;
|
|
`,
|
|
modalRow: css`
|
|
margin-bottom: ${theme.spacing(4)};
|
|
`,
|
|
modalTokenRow: css`
|
|
display: flex;
|
|
`,
|
|
modalCopyToClipboardButton: css`
|
|
margin-left: ${theme.spacing(0.5)};
|
|
`,
|
|
modalHeaderTitle: css`
|
|
font-size: ${theme.typography.size.lg};
|
|
margin: ${theme.spacing(0, 4, 0, 1)};
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
top: 2px;
|
|
`,
|
|
modalHeaderIcon: css`
|
|
margin-right: ${theme.spacing(2)};
|
|
font-size: inherit;
|
|
&:before {
|
|
vertical-align: baseline;
|
|
}
|
|
`,
|
|
};
|
|
};
|