diff --git a/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx b/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx index 31fd1ad..71ee7f4 100644 --- a/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx +++ b/web/src/components/backup-tasks/BackupTaskFormDrawer.tsx @@ -1,5 +1,5 @@ import { Alert, Button, Divider, Drawer, Input, InputNumber, Select, Space, Steps, Switch, Typography, Grid } from '@arco-design/web-react' -import { IconDelete, IconPlus } from '@arco-design/web-react/icon' +import { IconDelete, IconPlus } from '../icons' import { useEffect, useMemo, useState } from 'react' import { CronInput } from '../CronInput' import type { StorageTargetDetail, StorageTargetPayload, StorageTargetSummary } from '../../types/storage-targets' @@ -9,6 +9,7 @@ import type { NodeSummary } from '../../types/nodes' import { DatabasePicker } from '../common/DatabasePicker' import { DirectoryPicker } from '../common/DirectoryPicker' import { StorageTargetFormDrawer } from '../storage-targets/StorageTargetFormDrawer' +import { StorageTargetName } from '../storage-targets/StorageTargetName' import { SourceServerSelector } from './SourceServerSelector' import { backupCompressionOptions, @@ -169,7 +170,7 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa return 0 }) return sorted.map((item) => ({ - label: item.starred ? `★ ${item.name}` : item.name, + label: , value: item.id, disabled: !item.enabled, })) diff --git a/web/src/components/common/DirectoryPicker.tsx b/web/src/components/common/DirectoryPicker.tsx index c24b15a..5129868 100644 --- a/web/src/components/common/DirectoryPicker.tsx +++ b/web/src/components/common/DirectoryPicker.tsx @@ -1,5 +1,5 @@ import { Button, Input, Message, Modal, Space, Spin, Tree, Typography, Empty } from '@arco-design/web-react' -import { IconFolder, IconFile, IconFolderAdd } from '@arco-design/web-react/icon' +import { IconFolder, IconFile, IconFolderAdd } from '../icons' import { useCallback, useState } from 'react' import { listNodeDirectory } from '../../services/nodes' import type { DirEntry } from '../../types/nodes' diff --git a/web/src/components/common/EventCenter.tsx b/web/src/components/common/EventCenter.tsx index 42561cf..fa20f93 100644 --- a/web/src/components/common/EventCenter.tsx +++ b/web/src/components/common/EventCenter.tsx @@ -1,5 +1,5 @@ import { Badge, Button, Drawer, Empty, Notification, Space, Tag, Typography } from '@arco-design/web-react' -import { IconNotification } from '@arco-design/web-react/icon' +import { IconNotification } from '../icons' import { useEffect, useState } from 'react' import { useEventStream, type SystemEvent } from '../../hooks/useEventStream' import { useEventStore } from '../../stores/events' diff --git a/web/src/components/common/GlobalSearch.tsx b/web/src/components/common/GlobalSearch.tsx index 3a319a2..dcfc2bf 100644 --- a/web/src/components/common/GlobalSearch.tsx +++ b/web/src/components/common/GlobalSearch.tsx @@ -1,5 +1,5 @@ import { Empty, Input, Modal, Space, Spin, Tag, Typography } from '@arco-design/web-react' -import { IconSearch } from '@arco-design/web-react/icon' +import { IconSearch } from '../icons' import { useCallback, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { globalSearch, type SearchKind, type SearchResult, type SearchResultItem } from '../../services/search' diff --git a/web/src/components/icons/BackupServerIllustration.tsx b/web/src/components/icons/BackupServerIllustration.tsx new file mode 100644 index 0000000..7bb3260 --- /dev/null +++ b/web/src/components/icons/BackupServerIllustration.tsx @@ -0,0 +1,50 @@ +import type { SVGProps } from 'react' + +export function BackupServerIllustration(props: SVGProps) { + return ( + + ) +} diff --git a/web/src/components/icons/index.ts b/web/src/components/icons/index.ts new file mode 100644 index 0000000..c0c9f4c --- /dev/null +++ b/web/src/components/icons/index.ts @@ -0,0 +1,38 @@ +export { + IconBook, + IconCheckCircle, + IconCloud, + IconCloudDownload, + IconCommand, + IconCopy, + IconDashboard, + IconDelete, + IconDesktop, + IconDown, + IconDownload, + IconEdit, + IconFile, + IconFilePdf, + IconFolder, + IconFolderAdd, + IconHistory, + IconInfoCircle, + IconList, + IconLock, + IconMenuFold, + IconMenuUnfold, + IconMore, + IconNotification, + IconPlus, + IconPoweroff, + IconRefresh, + IconSafe, + IconSave, + IconSearch, + IconSettings, + IconStarFill, + IconStorage, + IconUser, +} from '@arco-design/web-react/icon' + +export { BackupServerIllustration } from './BackupServerIllustration' diff --git a/web/src/components/storage-targets/StorageTargetName.test.tsx b/web/src/components/storage-targets/StorageTargetName.test.tsx new file mode 100644 index 0000000..f9b8314 --- /dev/null +++ b/web/src/components/storage-targets/StorageTargetName.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { StorageTargetName } from './StorageTargetName' + +describe('StorageTargetName', () => { + it('uses an SVG icon for starred targets without character symbols', () => { + const { container } = render() + + expect(screen.getByText('Central storage')).toBeInTheDocument() + expect(screen.getByLabelText('Central storage,已收藏')).toBeInTheDocument() + expect(container.querySelector('svg')).not.toBeNull() + expect(container.textContent).not.toContain(String.fromCodePoint(0x2605)) + }) +}) diff --git a/web/src/components/storage-targets/StorageTargetName.tsx b/web/src/components/storage-targets/StorageTargetName.tsx new file mode 100644 index 0000000..c14ac9e --- /dev/null +++ b/web/src/components/storage-targets/StorageTargetName.tsx @@ -0,0 +1,18 @@ +import { IconStarFill } from '../icons' + +interface StorageTargetNameProps { + name: string + starred?: boolean +} + +export function StorageTargetName({ name, starred = false }: StorageTargetNameProps) { + return ( + + {starred ? : null} + {name} + + ) +} diff --git a/web/src/layouts/AppLayout.tsx b/web/src/layouts/AppLayout.tsx index e5db9a6..d042549 100644 --- a/web/src/layouts/AppLayout.tsx +++ b/web/src/layouts/AppLayout.tsx @@ -21,7 +21,7 @@ import { IconDesktop, IconList, IconFilePdf, -} from '@arco-design/web-react/icon' +} from '../components/icons' import { useState } from 'react' import { Outlet, useLocation, useNavigate } from 'react-router-dom' import { diff --git a/web/src/layouts/protected-layout.tsx b/web/src/layouts/protected-layout.tsx index abc81e7..6c15661 100644 --- a/web/src/layouts/protected-layout.tsx +++ b/web/src/layouts/protected-layout.tsx @@ -9,7 +9,7 @@ import { IconDashboard, IconInfoCircle, IconPoweroff, -} from '@arco-design/web-react/icon'; +} from '../components/icons'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { useAuthStore } from '../stores/auth'; diff --git a/web/src/pages/dashboard/DashboardPage.tsx b/web/src/pages/dashboard/DashboardPage.tsx index 9cc99d0..1c578af 100644 --- a/web/src/pages/dashboard/DashboardPage.tsx +++ b/web/src/pages/dashboard/DashboardPage.tsx @@ -1,5 +1,5 @@ import { Alert, Avatar, Card, Empty, Grid, PageHeader, Space, Table, Tag, Typography } from '@arco-design/web-react' -import { IconCheckCircle, IconDesktop, IconHistory, IconSafe, IconSave, IconStorage } from '@arco-design/web-react/icon' +import { IconCheckCircle, IconDesktop, IconHistory, IconSafe, IconSave, IconStorage } from '../../components/icons' import ReactEChartsCore from 'echarts-for-react/lib/core' import * as echarts from 'echarts/core' import { BarChart, LineChart, PieChart } from 'echarts/charts' diff --git a/web/src/pages/login/LoginPage.tsx b/web/src/pages/login/LoginPage.tsx index e854223..1c01c17 100644 --- a/web/src/pages/login/LoginPage.tsx +++ b/web/src/pages/login/LoginPage.tsx @@ -1,5 +1,5 @@ import { Button, Checkbox, Form, Input, Space, Typography, Message } from '@arco-design/web-react' -import { IconCloud, IconLock, IconSafe, IconUser } from '@arco-design/web-react/icon' +import { BackupServerIllustration, IconCloud, IconLock, IconSafe, IconUser } from '../../components/icons' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -200,41 +200,7 @@ export function LoginPage() {
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {t('auth.bannerTitle')} diff --git a/web/src/pages/nodes/BatchCommandTable.tsx b/web/src/pages/nodes/BatchCommandTable.tsx index b96f82d..c724e45 100644 --- a/web/src/pages/nodes/BatchCommandTable.tsx +++ b/web/src/pages/nodes/BatchCommandTable.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import { Table, Button, Space, Message, Typography, Tag } from '@arco-design/web-react' -import { IconCopy, IconDownload, IconRefresh } from '@arco-design/web-react/icon' +import { IconCopy, IconDownload, IconRefresh } from '../../components/icons' const { Text } = Typography diff --git a/web/src/pages/nodes/NodesPage.tsx b/web/src/pages/nodes/NodesPage.tsx index 9587078..bdcd079 100644 --- a/web/src/pages/nodes/NodesPage.tsx +++ b/web/src/pages/nodes/NodesPage.tsx @@ -5,7 +5,7 @@ import { } from '@arco-design/web-react' import { IconPlus, IconDelete, IconDesktop, IconCloudDownload, IconEdit, IconMore, -} from '@arco-design/web-react/icon' +} from '../../components/icons' import type { NodeSummary } from '../../types/nodes' import { listNodes, deleteNode, updateNode, rotateNodeToken } from '../../services/nodes' import { fetchSystemInfo } from '../../services/system' diff --git a/web/src/pages/nodes/wizard/Step3CommandPreview.tsx b/web/src/pages/nodes/wizard/Step3CommandPreview.tsx index 9000060..455119f 100644 --- a/web/src/pages/nodes/wizard/Step3CommandPreview.tsx +++ b/web/src/pages/nodes/wizard/Step3CommandPreview.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import { Typography, Button, Space, Collapse, Spin, Message, Tag } from '@arco-design/web-react' -import { IconCopy, IconRefresh } from '@arco-design/web-react/icon' +import { IconCopy, IconRefresh } from '../../../components/icons' import { fetchScriptPreview } from '../../../services/nodes' import type { InstallTokenResult, InstallMode } from '../../../types/nodes' import { buildAgentDownloadCommand, buildAgentInstallCommand, buildEmbeddedAgentInstallCommand } from '../installCommands' diff --git a/web/src/pages/reports/ReportsPage.tsx b/web/src/pages/reports/ReportsPage.tsx index d26d6d7..d6fec7b 100644 --- a/web/src/pages/reports/ReportsPage.tsx +++ b/web/src/pages/reports/ReportsPage.tsx @@ -1,5 +1,5 @@ import { Button, Card, Grid, Message, Select, Space, Statistic, Table, Tag, Typography } from '@arco-design/web-react' -import { IconDownload, IconRefresh } from '@arco-design/web-react/icon' +import { IconDownload, IconRefresh } from '../../components/icons' import { useCallback, useEffect, useState } from 'react' import { downloadComplianceCSV, fetchComplianceReport } from '../../services/reports' import type { ComplianceReport, ComplianceRisk, ComplianceTaskRow } from '../../types/reports' diff --git a/web/src/pages/storage-targets/StorageTargetsPage.tsx b/web/src/pages/storage-targets/StorageTargetsPage.tsx index c987f0a..7e062d1 100644 --- a/web/src/pages/storage-targets/StorageTargetsPage.tsx +++ b/web/src/pages/storage-targets/StorageTargetsPage.tsx @@ -18,6 +18,7 @@ import { formatBytes } from '../../utils/format' import type { StorageConnectionTestResult, StorageTargetDetail, StorageTargetPayload, StorageTargetSummary } from '../../types/storage-targets' import { getStorageTargetTypeLabel } from '../../components/storage-targets/field-config' import { StorageTargetFormDrawer } from '../../components/storage-targets/StorageTargetFormDrawer' +import { StorageTargetName } from '../../components/storage-targets/StorageTargetName' function resolveErrorMessage(error: unknown) { if (axios.isAxiosError(error)) { @@ -224,7 +225,7 @@ export function StorageTargetsPage() {
- {target.starred ? '★ ' : ''}{target.name} + {getStorageTargetTypeLabel(target.type) && {getStorageTargetTypeLabel(target.type)}}