Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 63 additions & 15 deletions client/src/pages/databases/collections/StatusAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import Icons from '@/components/icons/Icons';
import CustomToolTip from '@/components/customToolTip/CustomToolTip';
import LoadCollectionDialog from '@/pages/dialogs/LoadCollectionDialog';
import ReleaseCollectionDialog from '@/pages/dialogs/ReleaseCollectionDialog';
import LoadPartitionDialog from '@/pages/dialogs/LoadPartitionDialog';
import ReleasePartitionDialog from '@/pages/dialogs/ReleasePartitionDialog';
import type { PartitionData } from '@server/types';

// 扩展类型,添加 partition 和 onRefresh 属性
type ExtendedStatusActionType = StatusActionType & {
partition?: PartitionData;
onRefresh?: () => void;
};

const StatusIndicator: FC<{ color: string; filled?: boolean }> = ({
color,
Expand All @@ -27,7 +36,7 @@ const StatusIndicator: FC<{ color: string; filled?: boolean }> = ({
/>
);

const StatusAction: FC<StatusActionType> = props => {
const StatusAction: FC<ExtendedStatusActionType> = props => {
const {
status,
percentage = 0,
Expand All @@ -36,6 +45,8 @@ const StatusAction: FC<StatusActionType> = props => {
showLoadButton,
createIndexElement,
sx,
partition,
onRefresh,
} = props;

const theme = useTheme();
Expand All @@ -47,6 +58,8 @@ const StatusAction: FC<StatusActionType> = props => {
paddingLeft: theme.spacing(0.5),
};

const isPartition = !!partition;

const LoadingIndicator = () => (
<span
style={{
Expand Down Expand Up @@ -126,13 +139,29 @@ const StatusAction: FC<StatusActionType> = props => {
tooltip: collectionTrans('clickToLoad'),
variant: 'outlined' as const,
onClick: () => {
setDialog({
open: true,
type: 'custom',
params: {
component: <LoadCollectionDialog collection={collection} />,
},
});
if (isPartition && partition) {
setDialog({
open: true,
type: 'custom',
params: {
component: (
<LoadPartitionDialog
partitions={[partition]}
collectionName={collection.collection_name}
onRefresh={onRefresh}
/>
),
},
});
} else {
setDialog({
open: true,
type: 'custom',
params: {
component: <LoadCollectionDialog collection={collection} />,
},
});
}
},
},
[LOADING_STATE.LOADED]: {
Expand All @@ -141,13 +170,29 @@ const StatusAction: FC<StatusActionType> = props => {
tooltip: collectionTrans('clickToRelease'),
icon: <StatusIndicator color={theme.palette.primary.main} filled />,
onClick: () => {
setDialog({
open: true,
type: 'custom',
params: {
component: <ReleaseCollectionDialog collection={collection} />,
},
});
if (isPartition && partition) {
setDialog({
open: true,
type: 'custom',
params: {
component: (
<ReleasePartitionDialog
partitions={[partition]}
collectionName={collection.collection_name}
onRefresh={onRefresh}
/>
),
},
});
} else {
setDialog({
open: true,
type: 'custom',
params: {
component: <ReleaseCollectionDialog collection={collection} />,
},
});
}
},
},
[LOADING_STATE.LOADING]: {
Expand All @@ -174,6 +219,9 @@ const StatusAction: FC<StatusActionType> = props => {
commonTrans,
collection,
setDialog,
isPartition,
partition,
onRefresh,
]);

const renderStatusChip = () => {
Expand Down
61 changes: 60 additions & 1 deletion client/src/pages/databases/collections/partitions/Partitions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box } from '@mui/material';
import { Box, Typography } from '@mui/material';
import { useContext, useEffect, useState } from 'react';
import { useSearchParams, useParams } from 'react-router-dom';
import Highlighter from 'react-highlight-words';
Expand All @@ -15,6 +15,7 @@ import CreatePartitionDialog from '@/pages/dialogs/CreatePartitionDialog';
import DropPartitionDialog from '@/pages/dialogs/DropPartitionDialog';
import { formatNumber } from '@/utils';
import { getLabelDisplayedRows } from '@/pages/search/Utils';
import StatusAction from '@/pages/databases/collections/StatusAction';
import type { PartitionData, ResStatus } from '@server/types';

const Partitions = () => {
Expand Down Expand Up @@ -56,6 +57,19 @@ const Partitions = () => {
fetchPartitions(collectionName);
}, [collectionName]);

// 当有分区处于加载状态时,自动轮询刷新
useEffect(() => {
const hasLoadingPartition = partitions.some(p => p.status === 'loading');

if (hasLoadingPartition) {
const interval = setInterval(() => {
fetchPartitions(collectionName);
}, 2000); // 每2秒刷新一次

return () => clearInterval(interval);
}
}, [partitions, collectionName]);

const list = search
? partitions.filter(p => p.name.includes(search))
: partitions;
Expand Down Expand Up @@ -203,6 +217,51 @@ const Partitions = () => {
},
label: t('name'),
},
{
id: 'status',
align: 'left',
disablePadding: false,
label: '状态',
formatter(partition) {
const partitionAsCollection = {
collection_name: collectionName,
schema: {
hasVectorIndex: true,
fields: [],
primaryField: {} as any,
vectorFields: [],
scalarFields: [],
dynamicFields: [],
functionFields: [],
enablePartitionKey: false,
},
rowCount: 0,
createdTime: 0,
aliases: [],
description: '',
autoID: false,
id: String(partition.id),
loadedPercentage: partition.loadedPercentage || 0,
consistency_level: 'Bounded',
replicas: [],
status: partition.status || 'unloaded',
loaded: partition.status === 'loaded',
properties: [],
db_name: 'default',
} as any;

return (
<StatusAction
status={partition.status || 'unloaded'}
percentage={partition.loadedPercentage || 0}
collection={partitionAsCollection}
partition={partition}
showLoadButton={true}
onRefresh={() => fetchPartitions(collectionName)}
/>
);
},
},
{
id: 'rowCount',
align: 'left',
Expand Down
61 changes: 61 additions & 0 deletions client/src/pages/dialogs/LoadPartitionDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { FC, useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { rootContext } from '@/context';
import { PartitionService } from '@/http';
import DialogTemplate from '@/components/customDialog/DialogTemplate';
import type { PartitionData } from '@server/types';
import { Typography } from '@mui/material';

interface LoadPartitionDialogProps {
partitions: PartitionData[];
collectionName: string;
onRefresh?: () => void;
}

const LoadPartitionDialog: FC<LoadPartitionDialogProps> = (props) => {
const { partitions, collectionName, onRefresh } = props;
const { handleCloseDialog, openSnackBar } = useContext(rootContext);
const { t: partitionTrans } = useTranslation('partition');
const { t: dialogTrans } = useTranslation('dialog');
const { t: btnTrans } = useTranslation('btn');
const { t: successTrans } = useTranslation('success');
const [loading, setLoading] = useState(false);

const handleLoad = async () => {
try {
setLoading(true);
await PartitionService.loadPartition({
collectionName,
partitionNames: partitions.map(p => p.name),
});

handleCloseDialog();
openSnackBar(successTrans('load', { name: partitionTrans('partition') }));

if (onRefresh) {
onRefresh();
}
} catch (error) {
console.error('Failed to load partition:', error);
} finally {
setLoading(false);
}
};

return (
<DialogTemplate
title={dialogTrans('loadTitle', { type: partitionTrans('partition') })}
handleClose={handleCloseDialog}
children={
<Typography variant="body1" component="p">
{partitionTrans('loadContent')}
</Typography>
}
confirmLabel={btnTrans('load')}
handleConfirm={handleLoad}
confirmDisabled={loading}
/>
);
};

export default LoadPartitionDialog;
74 changes: 74 additions & 0 deletions client/src/pages/dialogs/ReleasePartitionDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { FC, useContext, useState } from 'react';
import { Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { rootContext } from '@/context';
import DialogTemplate from '@/components/customDialog/DialogTemplate';
import { PartitionService } from '@/http';
import type { PartitionData } from '@server/types';

interface ReleasePartitionDialogProps {
partitions: PartitionData[];
collectionName: string;
onRefresh?: () => void;
}

const ReleasePartitionDialog: FC<ReleasePartitionDialogProps> = (props) => {
const { partitions, collectionName, onRefresh } = props;
const { handleCloseDialog, openSnackBar } = useContext(rootContext);
const { t: partitionTrans } = useTranslation('partition');
const { t: dialogTrans } = useTranslation('dialog');
const { t: btnTrans } = useTranslation('btn');
const { t: successTrans } = useTranslation('success');
const [disabled, setDisabled] = useState(false);

const handleConfirm = async () => {
setDisabled(true);
try {
await PartitionService.releasePartition({
collectionName,
partitionNames: partitions.map(p => p.name),
});

openSnackBar(successTrans('release', { name: partitionTrans('partition') }));

if (onRefresh) {
setTimeout(() => onRefresh(), 1000);
}

handleCloseDialog();
} finally {
setDisabled(false);
}
};

const partitionName = partitions.map(p => p.name).join(', ');

return (
<DialogTemplate
title={dialogTrans('releaseTitle', {
type: partitionName,
})}
handleClose={handleCloseDialog}
children={
<Typography
variant="body1"
component="p"
sx={{
margin: '8px 0 16px 0',
maxWidth: 480,
color: (theme) => theme.palette.text.secondary,
}}
>
{dialogTrans('releaseContent', {
type: partitionName,
})}
</Typography>
}
confirmLabel={btnTrans('release')}
handleConfirm={handleConfirm}
confirmDisabled={disabled}
/>
);
};

export default ReleasePartitionDialog;
Loading