Files
superset2/superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx
T

176 lines
4.6 KiB
TypeScript

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import { useEffect, useRef, useState } from 'react';
import { SupersetClient } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import { css, useTheme } from '@apache-superset/core/theme';
import { Alert } from '@apache-superset/core/components';
import {
FormModal,
FormItem,
Input,
Button,
Modal,
} from '@superset-ui/core/components';
import { useToasts } from 'src/components/MessageToasts/withToasts';
interface ApiKeyCreateModalProps {
show: boolean;
onHide: () => void;
onSuccess: () => void;
}
interface FormValues {
name: string;
}
export function ApiKeyCreateModal({
show,
onHide,
onSuccess,
}: ApiKeyCreateModalProps) {
const theme = useTheme();
const { addDangerToast, addSuccessToast } = useToasts();
const [createdKey, setCreatedKey] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(
() => () => {
if (copyTimerRef.current) {
clearTimeout(copyTimerRef.current);
}
},
[],
);
const handleFormSubmit = async (values: FormValues) => {
try {
const response = await SupersetClient.post({
endpoint: '/api/v1/security/api_keys/',
jsonPayload: values,
});
const key = response.json?.result?.key;
if (!key) {
throw new Error('API response did not include a key');
}
setCreatedKey(key);
addSuccessToast(t('API key created successfully'));
} catch (error) {
addDangerToast(t('Failed to create API key'));
throw error;
}
};
const handleCopyKey = async () => {
if (!createdKey) {
return;
}
try {
await navigator.clipboard.writeText(createdKey);
setCopied(true);
if (copyTimerRef.current) {
clearTimeout(copyTimerRef.current);
}
copyTimerRef.current = setTimeout(() => setCopied(false), 2000);
} catch {
addDangerToast(t('Failed to copy API key to clipboard'));
}
};
const handleClose = () => {
if (createdKey) {
onSuccess();
}
setCreatedKey(null);
setCopied(false);
onHide();
};
if (createdKey) {
return (
<Modal
show={show}
onHide={handleClose}
title={t('API Key Created')}
maskClosable={false}
closable={false}
footer={
<Button type="primary" onClick={handleClose}>
{t('Done')}
</Button>
}
>
<Alert
type="warning"
message={t('Save this API key securely')}
description={t(
'This is the only time you will see this key. Store it securely.',
)}
showIcon
css={css`
margin-bottom: ${theme.sizeUnit * 4}px;
`}
/>
<div
css={css`
display: flex;
gap: ${theme.sizeUnit * 2}px;
align-items: center;
`}
>
<Input
value={createdKey}
readOnly
css={css`
flex: 1;
font-family: monospace;
`}
/>
<Button onClick={handleCopyKey}>
{copied ? t('Copied!') : t('Copy')}
</Button>
</div>
</Modal>
);
}
return (
<FormModal
show={show}
onHide={handleClose}
title={t('Create API Key')}
onSave={() => {}}
formSubmitHandler={handleFormSubmit}
requiredFields={['name']}
>
<FormItem
name="name"
label={t('Name')}
rules={[{ required: true, message: t('API key name is required') }]}
>
<Input
name="name"
placeholder={t('e.g., CI/CD Pipeline, Analytics Script')}
/>
</FormItem>
</FormModal>
);
}