feat: deploy provider category

This commit is contained in:
Fu Diwei 2025-02-13 21:14:11 +08:00
parent 664bb692b6
commit 0b7b544d4e
7 changed files with 145 additions and 78 deletions

View File

@ -35,7 +35,7 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica
loading={loading} loading={loading}
placement="right" placement="right"
title={`Certificate #${data?.id}`} title={`Certificate #${data?.id}`}
width={640} width={720}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
> >
<Show when={!!data}> <Show when={!!data}>

View File

@ -1,9 +1,9 @@
import { memo, useEffect, useRef, useState } from "react"; import { memo, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd"; import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Tabs, Tooltip, Typography } from "antd";
import Show from "@/components/Show"; import Show from "@/components/Show";
import { deployProvidersMap } from "@/domain/provider"; import { DEPLOY_CATEGORIES, type DeployProvider, deployProvidersMap } from "@/domain/provider";
export type DeployProviderPickerProps = { export type DeployProviderPickerProps = {
className?: string; className?: string;
@ -24,15 +24,26 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele
} }
}, []); }, []);
const providers = Array.from(deployProvidersMap.values()); const [category, setCategory] = useState<string>(DEPLOY_CATEGORIES.ALL);
const filteredProviders = providers.filter((provider) => {
if (keyword) {
const value = keyword.toLowerCase();
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
}
return true; const providers = useMemo(() => {
}); return Array.from(deployProvidersMap.values())
.filter((provider) => {
if (keyword) {
const value = keyword.toLowerCase();
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
}
return true;
})
.filter((provider) => {
if (category && category !== DEPLOY_CATEGORIES.ALL) {
return provider.category === category;
}
return true;
});
}, [keyword, category]);
const handleProviderTypeSelect = (value: string) => { const handleProviderTypeSelect = (value: string) => {
onSelect?.(value); onSelect?.(value);
@ -43,29 +54,55 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele
<Input.Search ref={keywordInputRef} placeholder={placeholder} onChange={(e) => setKeyword(e.target.value.trim())} /> <Input.Search ref={keywordInputRef} placeholder={placeholder} onChange={(e) => setKeyword(e.target.value.trim())} />
<div className="mt-4"> <div className="mt-4">
<Show when={filteredProviders.length > 0} fallback={<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}> <Flex>
<Row gutter={[16, 16]}> <Tabs
{filteredProviders.map((provider, index) => { defaultActiveKey={DEPLOY_CATEGORIES.ALL}
return ( items={[
<Col key={index} xs={24} md={12} span={12}> DEPLOY_CATEGORIES.ALL,
<Card DEPLOY_CATEGORIES.CDN,
className="h-16 w-full overflow-hidden shadow-sm" DEPLOY_CATEGORIES.STORAGE,
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }} DEPLOY_CATEGORIES.LOADBALANCE,
hoverable DEPLOY_CATEGORIES.FIREWALL,
onClick={() => { DEPLOY_CATEGORIES.LIVE,
handleProviderTypeSelect(provider.type); DEPLOY_CATEGORIES.OTHER,
}} ].map((key) => ({
> key: key,
<Flex className="size-full overflow-hidden" align="center" gap={8}> label: t(`provider.category.${key}`),
<Avatar src={provider.icon} size="small" /> }))}
<Typography.Text className="line-clamp-2 flex-1">{t(provider.name)}</Typography.Text> size="small"
</Flex> tabBarStyle={{ marginLeft: "-1rem" }}
</Card> tabPosition="left"
</Col> onChange={(key) => setCategory(key)}
); />
})}
</Row> <div className="flex-1">
</Show> <Show when={providers.length > 0} fallback={<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}>
<Row gutter={[16, 16]}>
{providers.map((provider, index) => {
return (
<Col key={index} xs={24} md={12} span={12}>
<Card
className="h-16 w-full overflow-hidden shadow-sm"
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
hoverable
onClick={() => {
handleProviderTypeSelect(provider.type);
}}
>
<Tooltip title={t(provider.name)} mouseEnterDelay={1}>
<Flex className="size-full overflow-hidden" align="center" gap={8}>
<Avatar src={provider.icon} size="small" />
<Typography.Text className="line-clamp-2 flex-1">{t(provider.name)}</Typography.Text>
</Flex>
</Tooltip>
</Card>
</Col>
);
})}
</Row>
</Show>
</div>
</Flex>
</div> </div>
</div> </div>
); );

View File

@ -36,7 +36,7 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
loading={loading} loading={loading}
placement="right" placement="right"
title={`WorkflowRun #${data?.id}`} title={`WorkflowRun #${data?.id}`}
width={640} width={720}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
> >
<Show when={!!data}> <Show when={!!data}>

View File

@ -307,7 +307,7 @@ const SharedNodeConfigDrawer = ({
loading={loading} loading={loading}
open={open} open={open}
title={<div className="max-w-[480px] truncate">{node.name}</div>} title={<div className="max-w-[480px] truncate">{node.name}</div>}
width={640} width={720}
onClose={handleClose} onClose={handleClose}
> >
{children} {children}

View File

@ -219,11 +219,24 @@ export const DEPLOY_PROVIDERS = Object.freeze({
export type DeployProviderType = (typeof DEPLOY_PROVIDERS)[keyof typeof DEPLOY_PROVIDERS]; export type DeployProviderType = (typeof DEPLOY_PROVIDERS)[keyof typeof DEPLOY_PROVIDERS];
export const DEPLOY_CATEGORIES = Object.freeze({
ALL: "all",
CDN: "cdn",
STORAGE: "storage",
LOADBALANCE: "loadbalance",
FIREWALL: "firewall",
LIVE: "live",
OTHER: "other",
} as const);
export type DeployCategoryType = (typeof DEPLOY_CATEGORIES)[keyof typeof DEPLOY_CATEGORIES];
export type DeployProvider = { export type DeployProvider = {
type: DeployProviderType; type: DeployProviderType;
name: string; name: string;
icon: string; icon: string;
provider: AccessProviderType; provider: AccessProviderType;
category: DeployCategoryType;
}; };
export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProvider> = new Map( export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProvider> = new Map(
@ -232,51 +245,52 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
NOTICE: The following order determines the order displayed at the frontend. NOTICE: The following order determines the order displayed at the frontend.
*/ */
[ [
[DEPLOY_PROVIDERS.LOCAL, "provider.local"], [DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.SSH, "provider.ssh"], [DEPLOY_PROVIDERS.SSH, "provider.ssh", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.WEBHOOK, "provider.webhook"], [DEPLOY_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret"], [DEPLOY_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.ALIYUN_OSS, "provider.aliyun.oss"], [DEPLOY_PROVIDERS.ALIYUN_OSS, "provider.aliyun.oss", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.ALIYUN_CDN, "provider.aliyun.cdn"], [DEPLOY_PROVIDERS.ALIYUN_CDN, "provider.aliyun.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.ALIYUN_DCDN, "provider.aliyun.dcdn"], [DEPLOY_PROVIDERS.ALIYUN_DCDN, "provider.aliyun.dcdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.ALIYUN_ESA, "provider.aliyun.esa"], [DEPLOY_PROVIDERS.ALIYUN_ESA, "provider.aliyun.esa", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.ALIYUN_CLB, "provider.aliyun.clb"], [DEPLOY_PROVIDERS.ALIYUN_CLB, "provider.aliyun.clb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.ALIYUN_ALB, "provider.aliyun.alb"], [DEPLOY_PROVIDERS.ALIYUN_ALB, "provider.aliyun.alb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.ALIYUN_NLB, "provider.aliyun.nlb"], [DEPLOY_PROVIDERS.ALIYUN_NLB, "provider.aliyun.nlb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.ALIYUN_WAF, "provider.aliyun.waf"], [DEPLOY_PROVIDERS.ALIYUN_WAF, "provider.aliyun.waf", DEPLOY_CATEGORIES.FIREWALL],
[DEPLOY_PROVIDERS.ALIYUN_LIVE, "provider.aliyun.live"], [DEPLOY_PROVIDERS.ALIYUN_LIVE, "provider.aliyun.live", DEPLOY_CATEGORIES.LIVE],
[DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY, "provider.aliyun.cas_deploy"], [DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY, "provider.aliyun.cas_deploy", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos"], [DEPLOY_PROVIDERS.TENCENTCLOUD_COS, "provider.tencentcloud.cos", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.TENCENTCLOUD_CDN, "provider.tencentcloud.cdn"], [DEPLOY_PROVIDERS.TENCENTCLOUD_CDN, "provider.tencentcloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.TENCENTCLOUD_ECDN, "provider.tencentcloud.ecdn"], [DEPLOY_PROVIDERS.TENCENTCLOUD_ECDN, "provider.tencentcloud.ecdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.TENCENTCLOUD_EO, "provider.tencentcloud.eo"], [DEPLOY_PROVIDERS.TENCENTCLOUD_EO, "provider.tencentcloud.eo", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.TENCENTCLOUD_CLB, "provider.tencentcloud.clb"], [DEPLOY_PROVIDERS.TENCENTCLOUD_CLB, "provider.tencentcloud.clb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.TENCENTCLOUD_CSS, "provider.tencentcloud.css"], [DEPLOY_PROVIDERS.TENCENTCLOUD_CSS, "provider.tencentcloud.css", DEPLOY_CATEGORIES.LIVE],
[DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY, "provider.tencentcloud.ssl_deploy"], [DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY, "provider.tencentcloud.ssl_deploy", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.HUAWEICLOUD_CDN, "provider.huaweicloud.cdn"], [DEPLOY_PROVIDERS.HUAWEICLOUD_CDN, "provider.huaweicloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb"], [DEPLOY_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn"], [DEPLOY_PROVIDERS.BAIDUCLOUD_CDN, "provider.baiducloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.VOLCENGINE_TOS, "provider.volcengine.tos"], [DEPLOY_PROVIDERS.VOLCENGINE_TOS, "provider.volcengine.tos", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn"], [DEPLOY_PROVIDERS.VOLCENGINE_CDN, "provider.volcengine.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn"], [DEPLOY_PROVIDERS.VOLCENGINE_DCDN, "provider.volcengine.dcdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.VOLCENGINE_CLB, "provider.volcengine.clb"], [DEPLOY_PROVIDERS.VOLCENGINE_CLB, "provider.volcengine.clb", DEPLOY_CATEGORIES.LOADBALANCE],
[DEPLOY_PROVIDERS.VOLCENGINE_LIVE, "provider.volcengine.live"], [DEPLOY_PROVIDERS.VOLCENGINE_LIVE, "provider.volcengine.live", DEPLOY_CATEGORIES.LIVE],
[DEPLOY_PROVIDERS.QINIU_CDN, "provider.qiniu.cdn"], [DEPLOY_PROVIDERS.QINIU_CDN, "provider.qiniu.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.QINIU_PILI, "provider.qiniu.pili"], [DEPLOY_PROVIDERS.QINIU_PILI, "provider.qiniu.pili", DEPLOY_CATEGORIES.LIVE],
[DEPLOY_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud.cdn"], [DEPLOY_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.cdn"], [DEPLOY_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.UCLOUD_US3, "provider.ucloud.us3"], [DEPLOY_PROVIDERS.UCLOUD_US3, "provider.ucloud.us3", DEPLOY_CATEGORIES.STORAGE],
[DEPLOY_PROVIDERS.UCLOUD_UCDN, "provider.ucloud.ucdn"], [DEPLOY_PROVIDERS.UCLOUD_UCDN, "provider.ucloud.ucdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.AWS_CLOUDFRONT, "provider.aws.cloudfront"], [DEPLOY_PROVIDERS.AWS_CLOUDFRONT, "provider.aws.cloudfront", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site"], [DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications"], [DEPLOY_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOY_CATEGORIES.OTHER],
].map(([type, name]) => [ ].map(([type, name, category]) => [
type, type,
{ {
type: type as DeployProviderType, type: type as DeployProviderType,
name: name, name: name,
icon: accessProvidersMap.get(type.split("-")[0])!.icon, icon: accessProvidersMap.get(type.split("-")[0])!.icon,
provider: type.split("-")[0] as AccessProviderType, provider: type.split("-")[0] as AccessProviderType,
category: category as DeployCategoryType,
}, },
]) ])
); );

View File

@ -67,5 +67,13 @@
"provider.volcengine.live": "Volcengine - Live", "provider.volcengine.live": "Volcengine - Live",
"provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)", "provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)",
"provider.webhook": "Webhook", "provider.webhook": "Webhook",
"provider.westcn": "West.cn" "provider.westcn": "West.cn",
"provider.category.all": "All",
"provider.category.cdn": "CDN",
"provider.category.storage": "Storage",
"provider.category.loadbalance": "Load Balance",
"provider.category.firewall": "Firewall",
"provider.category.live": "Live",
"provider.category.other": "Other"
} }

View File

@ -67,5 +67,13 @@
"provider.volcengine.live": "火山引擎 - 视频直播 Live", "provider.volcengine.live": "火山引擎 - 视频直播 Live",
"provider.volcengine.tos": "火山引擎 - 对象存储 TOS", "provider.volcengine.tos": "火山引擎 - 对象存储 TOS",
"provider.webhook": "Webhook", "provider.webhook": "Webhook",
"provider.westcn": "西部数码" "provider.westcn": "西部数码",
"provider.category.all": "全部",
"provider.category.cdn": "CDN",
"provider.category.storage": "存储",
"provider.category.loadbalance": "负载均衡",
"provider.category.firewall": "防火墙",
"provider.category.live": "直播",
"provider.category.other": "其他"
} }