feat: support deploying cpu workers for k8s cluster
This commit is contained in:
@@ -70,7 +70,11 @@ export default {
|
|||||||
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
|
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'You can select multiple GPU Vendors or none for CPU-only clusters',
|
||||||
'clusters.addworker.checkEnv': 'Check Environment',
|
'clusters.addworker.checkEnv': 'Check Environment',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'Use the following command to verify that the Kubernetes cluster has at least one ready node. You are registering a CPU-only cluster.',
|
||||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||||
'clusters.addworker.dtkVersion': 'DTK Version',
|
'clusters.addworker.dtkVersion': 'DTK Version',
|
||||||
'clusters.addworker.runCommand': 'Run Command',
|
'clusters.addworker.runCommand': 'Run Command',
|
||||||
|
|||||||
@@ -70,7 +70,11 @@ export default {
|
|||||||
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
|
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'複数の GPU ベンダーを選択するか、CPU クラスター専用の場合は選択不要です',
|
||||||
'clusters.addworker.checkEnv': 'Check Environment',
|
'clusters.addworker.checkEnv': 'Check Environment',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'以下のコマンドを使用して、Kubernetes クラスターに少なくとも 1 つのレディーノードがあることを確認してください。CPU クラスターを登録しています。',
|
||||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||||
'clusters.addworker.dtkVersion': 'DTK バージョン',
|
'clusters.addworker.dtkVersion': 'DTK バージョン',
|
||||||
'clusters.addworker.runCommand': 'Run Command',
|
'clusters.addworker.runCommand': 'Run Command',
|
||||||
|
|||||||
@@ -70,7 +70,11 @@ export default {
|
|||||||
'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
||||||
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
|
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'Вы можете выбрать несколько производителей GPU или не выбирать для кластера только с CPU',
|
||||||
'clusters.addworker.checkEnv': 'Проверить окружение',
|
'clusters.addworker.checkEnv': 'Проверить окружение',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'Используйте следующую команду, чтобы убедиться, что в кластере Kubernetes есть хотя бы один готовый узел. Вы регистрируете кластер только с CPU.',
|
||||||
'clusters.addworker.specifyArgs': 'Указать аргументы',
|
'clusters.addworker.specifyArgs': 'Указать аргументы',
|
||||||
'clusters.addworker.dtkVersion': 'Версия DTK',
|
'clusters.addworker.dtkVersion': 'Версия DTK',
|
||||||
'clusters.addworker.runCommand': 'Выполнить команду',
|
'clusters.addworker.runCommand': 'Выполнить команду',
|
||||||
|
|||||||
@@ -70,7 +70,11 @@ export default {
|
|||||||
'<span class="bold-text">Docker dışı</span> kümeler için lütfen Kümeler sayfasından küme kaydı oluşturun veya işçi havuzlarını yönetin.',
|
'<span class="bold-text">Docker dışı</span> kümeler için lütfen Kümeler sayfasından küme kaydı oluşturun veya işçi havuzlarını yönetin.',
|
||||||
'clusters.addworker.selectGPU': 'GPU Üreticisi Seç',
|
'clusters.addworker.selectGPU': 'GPU Üreticisi Seç',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'Birden fazla GPU Üreticisi seçebilir veya yalnızca CPU kümeleri için hiçbirini seçmeyebilirsiniz',
|
||||||
'clusters.addworker.checkEnv': 'Ortamı Kontrol Et',
|
'clusters.addworker.checkEnv': 'Ortamı Kontrol Et',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'Kubernetes kümesinde en az bir hazır düğüm olduğunu doğrulamak için aşağıdaki komutu kullanın. Yalnızca CPU kümelerini kaydediyorsunuz.',
|
||||||
'clusters.addworker.specifyArgs': 'Argümanları Belirle',
|
'clusters.addworker.specifyArgs': 'Argümanları Belirle',
|
||||||
'clusters.addworker.dtkVersion': 'DTK Sürümü',
|
'clusters.addworker.dtkVersion': 'DTK Sürümü',
|
||||||
'clusters.addworker.runCommand': 'Komutu Çalıştır',
|
'clusters.addworker.runCommand': 'Komutu Çalıştır',
|
||||||
|
|||||||
@@ -68,7 +68,11 @@ export default {
|
|||||||
'<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。',
|
'<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。',
|
||||||
'clusters.addworker.selectGPU': '选择 GPU 厂商',
|
'clusters.addworker.selectGPU': '选择 GPU 厂商',
|
||||||
'clusters.addworker.selectGPU.multiTag': '可多选',
|
'clusters.addworker.selectGPU.multiTag': '可多选',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'可选择多个 GPU 厂商,或不选择以用于仅 CPU 的集群',
|
||||||
'clusters.addworker.checkEnv': '检查环境',
|
'clusters.addworker.checkEnv': '检查环境',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'使用以下命令验证 Kubernetes 集群中至少有一个就绪节点。你正在注册仅 CPU 的集群。',
|
||||||
'clusters.addworker.specifyArgs': '指定参数',
|
'clusters.addworker.specifyArgs': '指定参数',
|
||||||
'clusters.addworker.dtkVersion': 'DTK 版本',
|
'clusters.addworker.dtkVersion': 'DTK 版本',
|
||||||
'clusters.addworker.runCommand': '运行指令',
|
'clusters.addworker.runCommand': '运行指令',
|
||||||
|
|||||||
@@ -99,24 +99,30 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
|||||||
// Downstream steps (check env, run command, ...) only make sense after a
|
// Downstream steps (check env, run command, ...) only make sense after a
|
||||||
// GPU vendor has been chosen. If the user toggled off every vendor in
|
// GPU vendor has been chosen. If the user toggled off every vendor in
|
||||||
// multi-select, gate them shut so the wrong panel can't be opened.
|
// multi-select, gate them shut so the wrong panel can't be opened.
|
||||||
|
// Exception: for K8s clusters, Run Command is always accessible (CPU-only
|
||||||
|
// workers don't need a GPU vendor).
|
||||||
const selectedGPUs =
|
const selectedGPUs =
|
||||||
(summary.get('selectedGPUs') as string[] | undefined) || [];
|
(summary.get('selectedGPUs') as string[] | undefined) || [];
|
||||||
const currentGPU = (summary.get('currentGPU') as string | undefined) || '';
|
const currentGPU = (summary.get('currentGPU') as string | undefined) || '';
|
||||||
const noVendorSelected = !currentGPU && selectedGPUs.length === 0;
|
const noVendorSelected = !currentGPU && selectedGPUs.length === 0;
|
||||||
const downstreamDisabled = disabled || noVendorSelected;
|
const isK8s = provider === ProviderValueMap.Kubernetes;
|
||||||
|
// For non-K8s providers, all downstream steps are gated by vendor selection.
|
||||||
|
// For K8s, both CheckEnv and RunCommand are always accessible — CheckEnv
|
||||||
|
// shows a ready-nodes check when no vendor is selected (CPU-only workers).
|
||||||
|
const downstreamDisabled = disabled || (noVendorSelected && !isK8s);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// If the user just deselected everything, collapse any downstream
|
// If the user just deselected everything on a non-K8s provider, collapse
|
||||||
// panel back to the GPU step so they aren't left looking at a stale
|
// any downstream panel back to the GPU step so they aren't left looking at
|
||||||
// disabled-but-open command. Functional updater so we don't have to
|
// a stale disabled-but-open command. For K8s, CheckEnv stays open with a
|
||||||
// depend on `collapseKey` and re-run the effect on every toggle.
|
// ready-nodes check, so no collapsing needed.
|
||||||
if (!noVendorSelected) return;
|
if (!noVendorSelected || isK8s) return;
|
||||||
setCollapseKey((prev) =>
|
setCollapseKey((prev) =>
|
||||||
prev.has(StepNamesMap.SelectGPU)
|
prev.has(StepNamesMap.SelectGPU)
|
||||||
? prev
|
? prev
|
||||||
: new Set([StepNamesMap.SelectGPU])
|
: new Set([StepNamesMap.SelectGPU])
|
||||||
);
|
);
|
||||||
}, [noVendorSelected]);
|
}, [noVendorSelected, isK8s]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddWorkerContext.Provider
|
<AddWorkerContext.Provider
|
||||||
@@ -145,7 +151,9 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
|||||||
!stepList.includes(StepNamesMap.SelectCluster)) && (
|
!stepList.includes(StepNamesMap.SelectCluster)) && (
|
||||||
<>
|
<>
|
||||||
<SelectVendor disabled={disabled}></SelectVendor>
|
<SelectVendor disabled={disabled}></SelectVendor>
|
||||||
<CheckEnvironment disabled={downstreamDisabled}></CheckEnvironment>
|
{(isK8s || !noVendorSelected) && (
|
||||||
|
<CheckEnvironment disabled={disabled}></CheckEnvironment>
|
||||||
|
)}
|
||||||
|
|
||||||
{provider === ProviderValueMap.Kubernetes && (
|
{provider === ProviderValueMap.Kubernetes && (
|
||||||
<K8sRunCommand disabled={downstreamDisabled}></K8sRunCommand>
|
<K8sRunCommand disabled={downstreamDisabled}></K8sRunCommand>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { BulbOutlined } from '@ant-design/icons';
|
import { BulbOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Alert, Typography } from 'antd';
|
import { Alert, Typography } from 'antd';
|
||||||
|
import { ProviderValueMap } from '../../config';
|
||||||
import CheckEnvCommand from '../check-env-command';
|
import CheckEnvCommand from '../check-env-command';
|
||||||
import { useAddWorkerContext } from './add-worker-context';
|
import { useAddWorkerContext } from './add-worker-context';
|
||||||
import { AddWorkerStepProps, StepNamesMap } from './config';
|
import { AddWorkerStepProps, StepNamesMap } from './config';
|
||||||
@@ -10,8 +11,8 @@ import StepCollapse from './step-collapse';
|
|||||||
const CheckEnvironment: React.FC<AddWorkerStepProps> = ({ disabled }) => {
|
const CheckEnvironment: React.FC<AddWorkerStepProps> = ({ disabled }) => {
|
||||||
const { stepList, summary, provider } = useAddWorkerContext();
|
const { stepList, summary, provider } = useAddWorkerContext();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const currentGPU = summary.get('currentGPU');
|
const currentGPU = summary.get('currentGPU') as string;
|
||||||
const currentGPUs: string[] = summary.get('selectedGPUs') || [];
|
const currentGPUs: string[] = (summary.get('selectedGPUs') as string[]) || [];
|
||||||
const workerCommand = summary.get('workerCommand') || {
|
const workerCommand = summary.get('workerCommand') || {
|
||||||
label: '',
|
label: '',
|
||||||
link: '',
|
link: '',
|
||||||
@@ -19,6 +20,8 @@ const CheckEnvironment: React.FC<AddWorkerStepProps> = ({ disabled }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stepIndex = stepList.indexOf(StepNamesMap.CheckEnv) + 1;
|
const stepIndex = stepList.indexOf(StepNamesMap.CheckEnv) + 1;
|
||||||
|
const isK8s = provider === ProviderValueMap.Kubernetes;
|
||||||
|
const isCPUOnly = isK8s && !currentGPU && currentGPUs.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StepCollapse
|
<StepCollapse
|
||||||
@@ -31,27 +34,37 @@ const CheckEnvironment: React.FC<AddWorkerStepProps> = ({ disabled }) => {
|
|||||||
</Title>
|
</Title>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Alert
|
{isCPUOnly ? (
|
||||||
type="info"
|
<Typography.Paragraph style={{ marginBottom: 8 }}>
|
||||||
showIcon
|
{intl.formatMessage({
|
||||||
icon={<BulbOutlined />}
|
id: 'clusters.addworker.checkEnv.cpuOnlyTips'
|
||||||
style={{
|
})}
|
||||||
marginBottom: 8
|
</Typography.Paragraph>
|
||||||
}}
|
) : (
|
||||||
title={
|
<>
|
||||||
<span
|
<Alert
|
||||||
dangerouslySetInnerHTML={{
|
type="info"
|
||||||
__html: intl.formatMessage(
|
showIcon
|
||||||
{ id: 'clusters.create.addworker.tips' },
|
icon={<BulbOutlined />}
|
||||||
{ label: workerCommand.label, link: workerCommand.link }
|
style={{
|
||||||
)
|
marginBottom: 8
|
||||||
}}
|
}}
|
||||||
></span>
|
title={
|
||||||
}
|
<span
|
||||||
></Alert>
|
dangerouslySetInnerHTML={{
|
||||||
<Typography.Paragraph style={{ marginBottom: 8 }}>
|
__html: intl.formatMessage(
|
||||||
{intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })}
|
{ id: 'clusters.create.addworker.tips' },
|
||||||
</Typography.Paragraph>
|
{ label: workerCommand.label, link: workerCommand.link }
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
}
|
||||||
|
></Alert>
|
||||||
|
<Typography.Paragraph style={{ marginBottom: 8 }}>
|
||||||
|
{intl.formatMessage({ id: 'cluster.create.checkEnv.tips' })}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<CheckEnvCommand
|
<CheckEnvCommand
|
||||||
provider={provider}
|
provider={provider}
|
||||||
currentGPU={currentGPU}
|
currentGPU={currentGPU}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
GPUsConfigs
|
GPUsConfigs
|
||||||
} from '@/pages/resources/config/gpu-driver';
|
} from '@/pages/resources/config/gpu-driver';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tag } from 'antd';
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { ProviderValueMap } from '../../config';
|
import { ProviderValueMap } from '../../config';
|
||||||
import SupportedGPUs from '../support-gpus';
|
import SupportedGPUs from '../support-gpus';
|
||||||
@@ -94,7 +93,9 @@ const SelectVendor: React.FC<AddWorkerStepProps> = ({ disabled }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// init a default selection
|
// K8s clusters allow selecting none (CPU-only workers), so skip default
|
||||||
|
// selection. Other providers still default to NVIDIA.
|
||||||
|
if (multiCapable) return;
|
||||||
handleSelect(GPUDriverMap.NVIDIA, {
|
handleSelect(GPUDriverMap.NVIDIA, {
|
||||||
label: 'NVIDIA',
|
label: 'NVIDIA',
|
||||||
hiddenTitle: true,
|
hiddenTitle: true,
|
||||||
@@ -116,18 +117,18 @@ const SelectVendor: React.FC<AddWorkerStepProps> = ({ disabled }) => {
|
|||||||
{stepIndex}.{' '}
|
{stepIndex}.{' '}
|
||||||
{intl.formatMessage({ id: 'clusters.addworker.selectGPU' })}
|
{intl.formatMessage({ id: 'clusters.addworker.selectGPU' })}
|
||||||
{multiCapable && (
|
{multiCapable && (
|
||||||
<Tag
|
<span
|
||||||
color="blue"
|
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
borderRadius: 4
|
fontSize: 13,
|
||||||
|
color: 'var(--ant-color-text-secondary)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: 'clusters.addworker.selectGPU.multiTag'
|
id: 'clusters.addworker.selectGPU.subtitle'
|
||||||
})}
|
})}
|
||||||
</Tag>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Title>
|
</Title>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { addWorkerGuide } from '@/pages/resources/config';
|
import { addWorkerGuide } from '@/pages/resources/config';
|
||||||
import { HighlightCode } from '@gpustack/core-ui';
|
import { HighlightCode } from '@gpustack/core-ui';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ProviderType } from '../config';
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
|
|
||||||
|
// CPU-only (no GPU vendor selected) check for Kubernetes: verify at least one
|
||||||
|
// ready node exists in the target cluster.
|
||||||
|
const K8S_CPU_ONLY_CHECK =
|
||||||
|
'kubectl get nodes -o jsonpath=\'{.items[*].status.conditions[?(@.type=="Ready")].status}\' 2>/dev/null | grep -q "True" && echo "Ready nodes found. You are registering a CPU-only cluster." || (echo "No ready nodes found"; exit 1)';
|
||||||
|
|
||||||
type ViewModalProps = {
|
type ViewModalProps = {
|
||||||
provider: ProviderType;
|
provider: ProviderType;
|
||||||
@@ -25,6 +30,12 @@ const AddWorkerCommand: React.FC<ViewModalProps> = ({
|
|||||||
: currentGPU
|
: currentGPU
|
||||||
? [currentGPU]
|
? [currentGPU]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
// CPU-only K8s flow: no GPU vendor selected, check for ready nodes instead.
|
||||||
|
if (!keys.length && provider === ProviderValueMap.Kubernetes) {
|
||||||
|
return K8S_CPU_ONLY_CHECK;
|
||||||
|
}
|
||||||
|
|
||||||
if (!keys.length) return '';
|
if (!keys.length) return '';
|
||||||
const lines = keys
|
const lines = keys
|
||||||
.map((k) => configs.checkEnvCommand(k)?.[provider || ''])
|
.map((k) => configs.checkEnvCommand(k)?.[provider || ''])
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CheckOutlined } from '@ant-design/icons';
|
||||||
import { IconFont, TemplateCard } from '@gpustack/core-ui';
|
import { IconFont, TemplateCard } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
@@ -62,6 +63,29 @@ const Header = styled.div`
|
|||||||
const CardBox = styled.div`
|
const CardBox = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ActiveBadge = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--ant-color-primary);
|
||||||
|
clip-path: path('M 20 0 L 20 15 Q 20 20 15 20 L 0 20 Z');
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-right: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface ProviderCatalogProps {
|
interface ProviderCatalogProps {
|
||||||
@@ -160,6 +184,13 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
|
|||||||
header={renderTitle(action)}
|
header={renderTitle(action)}
|
||||||
icon={action.icon}
|
icon={action.icon}
|
||||||
></TemplateCard>
|
></TemplateCard>
|
||||||
|
{(Array.isArray(current)
|
||||||
|
? current.includes(action.key)
|
||||||
|
: current === action.key) && (
|
||||||
|
<ActiveBadge>
|
||||||
|
<CheckOutlined />
|
||||||
|
</ActiveBadge>
|
||||||
|
)}
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Box = styled.div`
|
|||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
background-color: var(--ant-color-fill-tertiary);
|
||||||
|
border-color: var(--ant-color-primary);
|
||||||
}
|
}
|
||||||
.template-card-icon {
|
.template-card-icon {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
@@ -57,6 +58,7 @@ const Box = styled.div`
|
|||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--ant-color-bg-solid);
|
background-color: var(--ant-color-bg-solid);
|
||||||
|
border-color: var(--ant-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user