-
{
- setOpen(true)
- }}>
- {getTarget()}
-
+
+ {getTarget()}
{
(foreRender || open) && setOpen(false)}
>{children}
}
-
)
-}
+})
-export default DrawerPicker
\ No newline at end of file
+export default memo(DrawerPicker)
\ No newline at end of file
diff --git a/src/components/status/Status.tsx b/src/components/status/Status.tsx
new file mode 100644
index 0000000..5415959
--- /dev/null
+++ b/src/components/status/Status.tsx
@@ -0,0 +1,59 @@
+import { Tag, TagProps } from 'antd'
+import { useTranslation } from '@/i18n.ts'
+import { SyncOutlined } from '@ant-design/icons'
+
+export interface StatusProps extends TagProps {
+ status: string
+}
+
+
+const getColor = (status: string) => {
+ if (status.includes('error') || status.includes('err')) {
+ return 'danger'
+ }
+ switch (status) {
+ case 'running':
+ return 'success'
+ case 'stopped':
+ return 'danger'
+ case 'unhealthy':
+ case 'paused':
+ case 'exited':
+ case 'dead':
+ case 'removing':
+ return 'warning'
+ default:
+ return 'primary'
+ }
+}
+
+const loadingStatus = [
+ 'installing',
+ 'building',
+ 'restarting',
+ 'upgrading',
+ 'rebuilding',
+ 'recreating',
+ 'creating',
+ 'starting',
+ 'removing',
+ 'applying',
+]
+
+const loadingIcon = (status: string): boolean => {
+ return loadingStatus.indexOf(status) > -1
+}
+export const Status = ({ status = 'running', ...props }: StatusProps) => {
+
+ const { t } = useTranslation()
+ const icon = loadingIcon(status) ?
: null
+ return (
+ <>
+
{t(`status.${status}`, status)}
+ >
+ )
+}
+
+export default Status
\ No newline at end of file
diff --git a/src/components/status/index.ts b/src/components/status/index.ts
new file mode 100644
index 0000000..b97fff9
--- /dev/null
+++ b/src/components/status/index.ts
@@ -0,0 +1 @@
+export * from './Status.tsx'
\ No newline at end of file
diff --git a/src/locales/lang/en-US.ts b/src/locales/lang/en-US.ts
index 592e5e6..1356777 100644
--- a/src/locales/lang/en-US.ts
+++ b/src/locales/lang/en-US.ts
@@ -48,6 +48,7 @@ export default {
news: 'Add newly',
add: 'Add',
cancel: 'Cancel',
+ ok: 'OK',
edit: 'Edit',
delete: 'Delete',
batchDel: 'Batch Delete',
diff --git a/src/locales/lang/status/zh-CN.ts b/src/locales/lang/status/zh-CN.ts
new file mode 100644
index 0000000..9f5ad9a
--- /dev/null
+++ b/src/locales/lang/status/zh-CN.ts
@@ -0,0 +1,41 @@
+export default {
+ running: '已启动',
+ done: '已完成',
+ success: '成功',
+ waiting: '执行中',
+ waiting1: '等待中',
+ failed: '失败',
+ stopped: '已停止',
+ error: '失败',
+ created: '已创建',
+ restarting: '重启中',
+ uploading: '上传中',
+ unhealthy: '异常',
+ removing: '移除中',
+ paused: '已暂停',
+ exited: '已停止',
+ dead: '已结束',
+ installing: '安装中',
+ enabled: '已启用',
+ disabled: '已停止',
+ normal: '正常',
+ building: '制作镜像中',
+ downloaderr: '下载失败',
+ upgrading: '升级中',
+ upgradeerr: '升级失败',
+ pullerr: '镜像拉取失败',
+ rebuilding: '重建中',
+ deny: '已屏蔽',
+ accept: '已放行',
+ used: '已使用',
+ unUsed: '未使用',
+ starting: '启动中',
+ recreating: '重建中',
+ creating: '创建中',
+ systemrestart: '中断',
+ init: '等待申请',
+ ready: '正常',
+ applying: '申请中',
+ applyerror: '失败',
+ syncerr: '失败',
+}
\ No newline at end of file
diff --git a/src/locales/lang/zh-CN.ts b/src/locales/lang/zh-CN.ts
index f0340cb..8f72057 100644
--- a/src/locales/lang/zh-CN.ts
+++ b/src/locales/lang/zh-CN.ts
@@ -1,87 +1,90 @@
import antdZh from 'antd/locale/zh_CN'
import menus from './pages/system/menus/zh-CN.ts'
import roles from './pages/system/roles/zh-CN.ts'
+import status from './status/zh-CN.ts'
export default {
- ...antdZh,
- error: {
- '404': {
- title: '无法找到',
- message: '找不到此页面'
+ ...antdZh,
+ status,
+ error: {
+ '404': {
+ title: '无法找到',
+ message: '找不到此页面'
+ },
+ '403': {
+ title: '没有权限',
+ message: '对不起,您没有权限查看此页面。'
+ },
+ 'error': {
+ title: '错误信息',
+ },
},
- '403': {
- title: '没有权限',
- message: '对不起,您没有权限查看此页面。'
+ route: {
+ goBack: '返回',
},
- 'error': {
- title: '错误信息',
+ app: {
+ header: {
+ logout: '退出登录',
+ }
+ },
+ login: {
+ title: '账户密码登录',
+ username: '用户名',
+ usernameMsg: '请输入用户名',
+ password: '密码',
+ passwordMsg: '请输入密码',
+ code: '验证码',
+ codeMsg: '请输入验证码',
+ submit: '登录',
+ success: '登录成功'
+ },
+ home: {
+ welcome: '欢迎使用'
},
- },
- route: {
- goBack: '返回',
- },
- app: {
- header: {
- logout: '退出登录',
- }
- },
- login: {
- title: '账户密码登录',
- username: '用户名',
- usernameMsg: '请输入用户名',
- password: '密码',
- passwordMsg: '请输入密码',
- code: '验证码',
- codeMsg: '请输入验证码',
- submit: '登录',
- success: '登录成功'
- },
- home: {
- welcome: '欢迎使用'
- },
- system: {
- menus,
- roles
- },
- actions: {
- news: '新增',
- add: '添加',
- edit: '编辑',
- cancel: '取消',
- delete: '删除',
- batchDel: '批量删除',
- reset: '重置',
- clear: '清空',
- close: '关闭',
- },
- message: {
- infoTitle: '提示',
- errorTitle: '错误',
- successTitle: '成功',
- warningTitle: '警告',
- batchDelete: '确定要删除所选数据吗?',
- deleteConfirm: '确定要删除吗?',
- success: '提交成功',
- fail: '提交失败',
- saveSuccess: '保存成功',
- editSuccess: '修改成功',
- deleteSuccess: '删除成功',
- saveFail: '保存失败',
- emptyData: '暂无数据',
- emptyDataAdd: '暂无数据,点击添加',
- required: '此项为必填项',
- },
- rules: {
- required: '此项为必填项',
- },
- tabs: {
- refresh: '刷新',
- maximize: '最大化',
- closeCurrent: '关闭当前',
- closeLeft: '关闭左侧',
- closeRight: '关闭右侧',
- closeOther: '关闭其它',
- closeAll: '关闭所有'
- }
+ system: {
+ menus,
+ roles
+ },
+ actions: {
+ news: '新增',
+ add: '添加',
+ edit: '编辑',
+ ok: '确定',
+ cancel: '取消',
+ delete: '删除',
+ batchDel: '批量删除',
+ reset: '重置',
+ clear: '清空',
+ close: '关闭',
+ },
+ message: {
+ infoTitle: '提示',
+ errorTitle: '错误',
+ successTitle: '成功',
+ warningTitle: '警告',
+ batchDelete: '确定要删除所选数据吗?',
+ deleteConfirm: '确定要删除吗?',
+ success: '提交成功',
+ fail: '提交失败',
+ saveSuccess: '保存成功',
+ editSuccess: '修改成功',
+ deleteSuccess: '删除成功',
+ saveFail: '保存失败',
+ emptyData: '暂无数据',
+ emptyDataAdd: '暂无数据,点击添加',
+ required: '此项为必填项',
+ },
+ rules: {
+ required: '此项为必填项',
+ },
+ tabs: {
+ refresh: '刷新',
+ maximize: '最大化',
+ closeCurrent: '关闭当前',
+ closeLeft: '关闭左侧',
+ closeRight: '关闭右侧',
+ closeOther: '关闭其它',
+ closeAll: '关闭所有'
+ }
}
diff --git a/src/pages/websites/ssl/ca/Detail.tsx b/src/pages/websites/ssl/ca/Detail.tsx
index b214aa9..7cbf23b 100644
--- a/src/pages/websites/ssl/ca/Detail.tsx
+++ b/src/pages/websites/ssl/ca/Detail.tsx
@@ -18,7 +18,7 @@ const Detail = (props: DrawerProps) => {
const options = useMemo(() => {
return [
{ label: t(`${prefix}.base`, '机构详情'), value: 'base' },
- { label: t(`${prefix}.src`, 'src'), value: 'csr' },
+ { label: t(`${prefix}.csr`, 'csr'), value: 'csr' },
{ label: t(`${prefix}.private_key`, '私钥'), value: 'private_key' },
]
}, [])
@@ -31,6 +31,15 @@ const Detail = (props: DrawerProps) => {
}
}, [ copyState ])
+ useEffect(() => {
+ return () => {
+ setUI({
+ open: false,
+ record: {},
+ })
+ }
+ }, [])
+
const render = useMemo(() => {
switch (key) {
case 'base':
diff --git a/src/pages/websites/ssl/components/Detail.tsx b/src/pages/websites/ssl/components/Detail.tsx
new file mode 100644
index 0000000..17b3fe8
--- /dev/null
+++ b/src/pages/websites/ssl/components/Detail.tsx
@@ -0,0 +1,127 @@
+import { Segmented, Drawer, DrawerProps, Input, Button, Flex, message, Descriptions } from 'antd'
+import { useEffect, useMemo, useState } from 'react'
+import { useTranslation } from '@/i18n.ts'
+import { useAtom } from 'jotai'
+import { detailAtom } from './store.ts'
+import { useStyle } from './style.ts'
+import { useCopyToClipboard } from 'react-use'
+import { getProvider } from '@/store/websites/ssl.ts'
+
+const Detail = (props: DrawerProps) => {
+
+
+ const prefix = 'website.ssl.detail'
+ const { t } = useTranslation()
+ const { styles } = useStyle()
+ const [ key, setKey ] = useState('base')
+ const [ ui, setUI ] = useAtom(detailAtom)
+ const [ copyState, setCopy ] = useCopyToClipboard()
+
+ const options = useMemo(() => {
+ return [
+ { label: t(`${prefix}.base`, '证书信息'), value: 'base' },
+ { label: t(`${prefix}.pem`, '证书'), value: 'pem' },
+ { label: t(`${prefix}.private_key`, '私钥'), value: 'private_key' },
+ ]
+ }, [])
+
+ useEffect(() => {
+ if (copyState.error) {
+ message.error(t('message.copyError', '复制失败'))
+ } else if (copyState.value) {
+ message.success(t('message.copySuccess', '复制成功'))
+ }
+ }, [ copyState ])
+
+ useEffect(() => {
+ return () => {
+ setUI({
+ open: false,
+ record: {},
+ })
+ }
+ }, [])
+
+ const render = useMemo(() => {
+ switch (key) {
+ case 'base':
+ return
+
+
+ {ui.record?.primary_domain}
+
+
+ {ui.record?.domains}
+
+
+ {ui.record?.type}
+
+
+ {ui.record?.organization}
+
+
+ {ui.record?.start_date}
+
+
+ {ui.record?.expire_date}
+
+
+ {getProvider(ui.record?.provider)}
+
+
+
+ case 'pem':
+ return
+
+
+
+
+
+ case 'private_key':
+ return
+
+
+
+
+
+ default:
+ return null
+ }
+ }, [ key, ui.record ])
+
+ return (
+
+ setUI(prev => ({
+ ...prev,
+ open: false
+ }))
+ }
+ >
+
+
+ {render}
+
+
+ )
+}
+
+export default Detail
\ No newline at end of file
diff --git a/src/pages/websites/ssl/components/Upload.tsx b/src/pages/websites/ssl/components/Upload.tsx
new file mode 100644
index 0000000..8ea70a6
--- /dev/null
+++ b/src/pages/websites/ssl/components/Upload.tsx
@@ -0,0 +1,77 @@
+import { Select, Form, Input } from 'antd'
+import { useTranslation } from '@/i18n.ts'
+import { useAtom } from 'jotai'
+import { uploadAtom, uploadType } from './store.ts'
+import { Else, If, Then } from 'react-if'
+import { MutableRefObject, useEffect } from 'react'
+import { FormInstance } from 'antd/lib'
+
+export interface UploadProps {
+ form?: FormInstance
+ formRef?: MutableRefObject
+}
+
+const Upload = (props: UploadProps) => {
+ const { t } = useTranslation()
+ const prefix = 'website.ssl.upload'
+ const [ form ] = Form.useForm()
+ const [ dto, updateDto ] = useAtom(uploadAtom)
+
+ useEffect(() => {
+ form.setFieldsValue(dto)
+ }, [])
+
+ props.formRef!.current = props.form ?? form
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Upload
\ No newline at end of file
diff --git a/src/pages/websites/ssl/components/store.ts b/src/pages/websites/ssl/components/store.ts
new file mode 100644
index 0000000..36c924a
--- /dev/null
+++ b/src/pages/websites/ssl/components/store.ts
@@ -0,0 +1,23 @@
+import { atom } from 'jotai'
+import { WebSite } from '@/types'
+import { t } from 'i18next'
+
+type DetailUI = {
+ open: boolean,
+ record?: WebSite.ISSL,
+
+}
+
+export const detailAtom = atom({
+ open: false,
+})
+
+export const uploadType = [
+ { label: t('website.ssl.upload.type_paste', '粘贴代码'), value: 'paste' },
+ { label: t('website.ssl.upload.type_local', '选择服务器文件'), value: 'local' },
+]
+
+
+export const uploadAtom = atom({
+ type: 'paste'
+})
diff --git a/src/pages/websites/ssl/components/style.ts b/src/pages/websites/ssl/components/style.ts
new file mode 100644
index 0000000..57e3d35
--- /dev/null
+++ b/src/pages/websites/ssl/components/style.ts
@@ -0,0 +1,23 @@
+import { createStyles } from '@/theme'
+
+export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
+ const prefix = `${prefixCls}-${token?.proPrefix}-ssl-detail`
+
+ const container = css`
+
+ `
+
+ const content = css`
+ padding: 20px 0;
+
+ .ant-descriptions-item-label{
+ font-weight: bold;
+ width: 50%;
+ }
+ `
+
+ return {
+ container: cx(prefix, props?.className, container),
+ content,
+ }
+})
\ No newline at end of file
diff --git a/src/pages/websites/ssl/index.tsx b/src/pages/websites/ssl/index.tsx
index 8f77f2b..ccebf0b 100644
--- a/src/pages/websites/ssl/index.tsx
+++ b/src/pages/websites/ssl/index.tsx
@@ -1,4 +1,4 @@
-import { useAtom, useAtomValue } from 'jotai'
+import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import {
deleteSslAtom, getProvider,
KeyTypeEnum,
@@ -7,15 +7,15 @@ import {
saveOrUpdateSslAtom,
sslListAtom,
sslPageAtom,
- sslSearchAtom
+ sslSearchAtom, uploadSslAtom
} from '@/store/websites/ssl.ts'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components'
-import { memo, useMemo, useState } from 'react'
+import { memo, useMemo, useRef, useState } from 'react'
import { useTranslation } from '@/i18n.ts'
-import { Button, Form, Popconfirm } from 'antd'
+import { Button, Form, Popconfirm, Space } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
-import DrawerPicker from '@/components/drawer-picker/DrawerPicker.tsx'
+import DrawerPicker, { DrawerPickerRef } from '@/components/drawer-picker/DrawerPicker.tsx'
import AcmeList from './acme/AcmeList.tsx'
import { acmeListAtom, AcmeType, getAcmeAccountTypeName } from '@/store/websites/acme.ts'
import { dnsListAtom, getDNSTypeName } from '@/store/websites/dns.ts'
@@ -23,12 +23,20 @@ import DNSList from './dns/DNSList.tsx'
import CAList from './ca/CAList.tsx'
import { WebSite } from '@/types'
import Switch from '@/components/switch'
+import { Else, If, Then } from 'react-if'
+import Action from '@/components/action/Action.tsx'
+import { Status } from '@/components/status'
+import SSLDetail from './components/Detail.tsx'
+import { detailAtom } from './components/store.ts'
+import Upload from './components/Upload.tsx'
+import { FormInstance } from 'antd/lib'
const SSL = () => {
const { t } = useTranslation()
const [ form ] = Form.useForm()
+ const uploadFormRef = useRef()
const [ page, setPage ] = useAtom(sslPageAtom)
const [ search, setSearch ] = useAtom(sslSearchAtom)
const { data: acmeData, isLoading: acmeLoading } = useAtomValue(acmeListAtom)
@@ -36,7 +44,9 @@ const SSL = () => {
const { data, isLoading, isFetching, refetch } = useAtomValue(sslListAtom)
const { mutate: saveOrUpdate, isSuccess, isPending: isSubmitting } = useAtomValue(saveOrUpdateSslAtom)
const { mutate: deleteSSL, isPending: isDeleting } = useAtomValue(deleteSslAtom)
-
+ const { mutate: uploadSSL, isSuccess: isUploadSuccess, isPending: isUploading } = useAtomValue(uploadSslAtom)
+ const updateDetail = useSetAtom(detailAtom)
+ const uploadDrawerRef = useRef()
const [ open, setOpen ] = useState(false)
const columns = useMemo[]>(() => {
@@ -80,6 +90,14 @@ const SSL = () => {
}
},
{
+ title: t('website.ssl.columns.status', '状态'),
+ dataIndex: 'status',
+ render: (_, record) => {
+ return
+ },
+ hideInForm: true,
+ },
+ {
title: t('website.ssl.columns.keyType', '密钥算法'),
dataIndex: 'key_type',
hideInTable: true,
@@ -196,19 +214,61 @@ const SSL = () => {
{
title: t('website.ssl.columns.expire_date', '过期时间'),
dataIndex: 'expire_date',
- valueType: 'dateTime'
+ valueType: 'dateTime',
+ hideInForm: true,
},
{
title: t('website.ssl.columns.option', '操作'), valueType: 'option',
key: 'option',
+ fixed: 'right',
+ width: 300,
render: (_, record) => [
- {
- }}
+ {
+ updateDetail({
+ open: true,
+ record
+ })
+ }}
>
- {t('actions.edit', '编辑')}
- ,
+ {t('actions.detail', '详情')}
+ ,
+ record.status !== 'manual'}>
+
+ {
+
+ }}
+ >
+ {t('actions.apply', '申请')}
+
+
+
+ {
+
+ }}
+ >
+ {t('actions.update', '更新')}
+
+
+
+ ,
+ {
+
+ }}
+ >
+ {t('actions.download', '下载')}
+ ,
{
}>
,
+ ,