|
|
@@ -1,8 +1,12 @@
|
|
|
import { CopyOutlined, DeleteOutlined, KeyOutlined } from '@ant-design/icons';
|
|
|
-import { Alert, Button, Card, Collapse, Descriptions, Form, Input, Modal, Popconfirm, Space, Table, Tag, Tooltip, Typography } from 'antd';
|
|
|
+import { Alert, Button, Collapse, Descriptions, Form, Input, Modal, Popconfirm, Space, Table, Tooltip, Typography } from 'antd';
|
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
|
import { useState } from 'react';
|
|
|
|
|
|
+import { CodeBlock } from '../components/common/CodeBlock';
|
|
|
+import { PageHeader } from '../components/common/PageHeader';
|
|
|
+import { SectionCard } from '../components/common/SectionCard';
|
|
|
+import { StatusPill } from '../components/common/StatusPill';
|
|
|
import {
|
|
|
buildApiUsageExamples,
|
|
|
canCopyFullApiToken,
|
|
|
@@ -68,57 +72,65 @@ export default function ApiTokens({ tokens, config, loading, onCreate, onDelete,
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- <Space direction="vertical" size={16} className="full-width">
|
|
|
- <Card title={t('tokens.createTitle')}>
|
|
|
- <Form form={form} layout="inline" onFinish={submit}>
|
|
|
- <Form.Item name="name" rules={[{ required: true, message: t('tokens.nameRequired') }]}>
|
|
|
- <Input placeholder={t('tokens.namePlaceholder')} />
|
|
|
- </Form.Item>
|
|
|
- <Button type="primary" htmlType="submit" loading={loading}>
|
|
|
- {t('tokens.create')}
|
|
|
- </Button>
|
|
|
- </Form>
|
|
|
- </Card>
|
|
|
- <Card title={t('tokens.listTitle')} extra={<Tag>{tokens.length}</Tag>}>
|
|
|
- <Alert type="info" showIcon message={t('tokens.prefixOnlyHelp')} className="token-list-alert" />
|
|
|
- <Table rowKey="id" columns={columns} dataSource={tokens} scroll={{ x: 900 }} />
|
|
|
- </Card>
|
|
|
- <Card title={t('tokens.docsTitle')}>
|
|
|
- <Space direction="vertical" size={16} className="full-width">
|
|
|
- <Alert type="info" showIcon message={t('tokens.noTokenHint')} />
|
|
|
- <Descriptions column={1} bordered size="small">
|
|
|
- <Descriptions.Item label={t('tokens.endpoint')}>{copyable(endpoint, onCopy)}</Descriptions.Item>
|
|
|
- <Descriptions.Item label={t('tokens.authHeader')}>{copyable(t('tokens.authHeaderValue'), onCopy)}</Descriptions.Item>
|
|
|
- <Descriptions.Item label={t('tokens.contentType')}><Typography.Text code>application/json</Typography.Text></Descriptions.Item>
|
|
|
- </Descriptions>
|
|
|
- <Card size="small" title={t('tokens.requestFields')}>
|
|
|
- <ul className="api-doc-list">
|
|
|
- <li>{t('tokens.fieldFrom')}</li>
|
|
|
- <li>{t('tokens.fieldTo')}</li>
|
|
|
- <li>{t('tokens.fieldSubject')}</li>
|
|
|
- <li>{t('tokens.fieldText')}</li>
|
|
|
- <li>{t('tokens.fieldHtml')}</li>
|
|
|
- </ul>
|
|
|
- </Card>
|
|
|
- <Collapse
|
|
|
- defaultActiveKey={['curl']}
|
|
|
- items={[
|
|
|
- { key: 'curl', label: t('tokens.curlExample'), children: <CodeSample value={examples.curl} /> },
|
|
|
- { key: 'node', label: t('tokens.nodeExample'), children: <CodeSample value={examples.nodeFetch} /> },
|
|
|
- { key: 'body', label: t('tokens.requestExample'), children: <CodeSample value={examples.requestBody} /> },
|
|
|
- { key: 'response', label: t('tokens.responseExample'), children: <CodeSample value={examples.successResponse} /> }
|
|
|
- ]}
|
|
|
- />
|
|
|
- <Card size="small" title={t('tokens.securityTips')}>
|
|
|
- <ul className="api-doc-list">
|
|
|
- <li>{t('tokens.securityTipStore')}</li>
|
|
|
- <li>{t('tokens.securityTipRotate')}</li>
|
|
|
- <li>{t('tokens.securityTipDomain')}</li>
|
|
|
- </ul>
|
|
|
- </Card>
|
|
|
- </Space>
|
|
|
- </Card>
|
|
|
- </Space>
|
|
|
+ <Space direction="vertical" size={20} className="full-width">
|
|
|
+ <PageHeader title={t('nav.tokens')} />
|
|
|
+
|
|
|
+ <SectionCard title={t('tokens.createTitle')}>
|
|
|
+ <Form form={form} layout="inline" onFinish={submit}>
|
|
|
+ <Form.Item name="name" rules={[{ required: true, message: t('tokens.nameRequired') }]}>
|
|
|
+ <Input placeholder={t('tokens.namePlaceholder')} />
|
|
|
+ </Form.Item>
|
|
|
+ <Button type="primary" htmlType="submit" loading={loading}>
|
|
|
+ {t('tokens.create')}
|
|
|
+ </Button>
|
|
|
+ </Form>
|
|
|
+ </SectionCard>
|
|
|
+
|
|
|
+ <SectionCard
|
|
|
+ title={t('tokens.listTitle')}
|
|
|
+ extra={<StatusPill tone="neutral">{tokens.length}</StatusPill>}
|
|
|
+ >
|
|
|
+ <Alert type="info" showIcon message={t('tokens.prefixOnlyHelp')} className="token-list-alert" />
|
|
|
+ <Table rowKey="id" columns={columns} dataSource={tokens} scroll={{ x: 900 }} />
|
|
|
+ </SectionCard>
|
|
|
+
|
|
|
+ <SectionCard title={t('tokens.docsTitle')}>
|
|
|
+ <Space direction="vertical" size={16} className="full-width">
|
|
|
+ <Alert type="info" showIcon message={t('tokens.noTokenHint')} />
|
|
|
+ <Descriptions column={1} bordered size="small">
|
|
|
+ <Descriptions.Item label={t('tokens.endpoint')}>{copyable(endpoint, onCopy)}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label={t('tokens.authHeader')}>{copyable(t('tokens.authHeaderValue'), onCopy)}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label={t('tokens.contentType')}><Typography.Text code>application/json</Typography.Text></Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ <SectionCard title={t('tokens.requestFields')}>
|
|
|
+ <ul className="api-doc-list">
|
|
|
+ <li>{t('tokens.fieldFrom')}</li>
|
|
|
+ <li>{t('tokens.fieldTo')}</li>
|
|
|
+ <li>{t('tokens.fieldSubject')}</li>
|
|
|
+ <li>{t('tokens.fieldText')}</li>
|
|
|
+ <li>{t('tokens.fieldHtml')}</li>
|
|
|
+ </ul>
|
|
|
+ </SectionCard>
|
|
|
+ <Collapse
|
|
|
+ defaultActiveKey={['curl']}
|
|
|
+ items={[
|
|
|
+ { key: 'curl', label: t('tokens.curlExample'), children: <CodeBlock value={examples.curl} onCopy={onCopy} /> },
|
|
|
+ { key: 'node', label: t('tokens.nodeExample'), children: <CodeBlock value={examples.nodeFetch} onCopy={onCopy} /> },
|
|
|
+ { key: 'body', label: t('tokens.requestExample'), children: <CodeBlock value={examples.requestBody} onCopy={onCopy} /> },
|
|
|
+ { key: 'response', label: t('tokens.responseExample'), children: <CodeBlock value={examples.successResponse} onCopy={onCopy} /> }
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ <SectionCard title={t('tokens.securityTips')}>
|
|
|
+ <ul className="api-doc-list">
|
|
|
+ <li>{t('tokens.securityTipStore')}</li>
|
|
|
+ <li>{t('tokens.securityTipRotate')}</li>
|
|
|
+ <li>{t('tokens.securityTipDomain')}</li>
|
|
|
+ </ul>
|
|
|
+ </SectionCard>
|
|
|
+ </Space>
|
|
|
+ </SectionCard>
|
|
|
+ </Space>
|
|
|
+
|
|
|
<Modal
|
|
|
title={t('tokens.createdTitle')}
|
|
|
open={Boolean(createdToken)}
|
|
|
@@ -141,10 +153,16 @@ export default function ApiTokens({ tokens, config, loading, onCreate, onDelete,
|
|
|
]}
|
|
|
>
|
|
|
<Space direction="vertical" size={16} className="full-width">
|
|
|
- <Alert type="warning" showIcon message={t('tokens.createdWarning')} />
|
|
|
- <Typography.Paragraph code copyable className="code-sample">
|
|
|
- {getCreatedApiTokenSecret(createdToken || {})}
|
|
|
- </Typography.Paragraph>
|
|
|
+ <Alert
|
|
|
+ type="error"
|
|
|
+ showIcon
|
|
|
+ message={t('tokens.createdWarning')}
|
|
|
+ className="token-secret-alert"
|
|
|
+ />
|
|
|
+ <CodeBlock
|
|
|
+ value={getCreatedApiTokenSecret(createdToken || {}) || ''}
|
|
|
+ onCopy={onCopy}
|
|
|
+ />
|
|
|
</Space>
|
|
|
</Modal>
|
|
|
</>
|
|
|
@@ -159,11 +177,3 @@ function copyable(value: string, onCopy: (value: string) => void) {
|
|
|
</Space>
|
|
|
);
|
|
|
}
|
|
|
-
|
|
|
-function CodeSample({ value }: { value: string }) {
|
|
|
- return (
|
|
|
- <Typography.Paragraph code copyable className="code-sample">
|
|
|
- {value}
|
|
|
- </Typography.Paragraph>
|
|
|
- );
|
|
|
-}
|