fix: logs are not fully loaded

This commit is contained in:
jialin
2025-11-30 20:33:16 +08:00
parent 306daf7591
commit 769cc09906
14 changed files with 133 additions and 111 deletions
-1
View File
@@ -4,7 +4,6 @@ import { compressionPluginConfig, monacoPluginConfig } from './plugins';
import proxy from './proxy'; import proxy from './proxy';
import routes from './routes'; import routes from './routes';
import { getBranchInfo } from './utils'; import { getBranchInfo } from './utils';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const versionInfo = getBranchInfo(); const versionInfo = getBranchInfo();
process.env.VERSION = JSON.stringify(versionInfo); process.env.VERSION = JSON.stringify(versionInfo);
+3 -3
View File
@@ -64,9 +64,9 @@ const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
const handleOnWheel = useCallback( const handleOnWheel = useCallback(
(e: any) => { (e: any) => {
const scrollTop = scrollEventElement?.scrollTop; const scrollTop = scrollEventElement?.current.scrollTop;
const scrollHeight = scrollEventElement?.scrollHeight; const scrollHeight = scrollEventElement?.current.scrollHeight;
const clientHeight = scrollEventElement?.clientHeight; const clientHeight = scrollEventElement?.current.clientHeight;
stopScroll.current = scrollTop + clientHeight <= scrollHeight; stopScroll.current = scrollTop + clientHeight <= scrollHeight;
@@ -1,4 +1,5 @@
import useSetChunkFetch from '@/hooks/use-chunk-fetch'; import useSetChunkFetch from '@/hooks/use-chunk-fetch';
import { useMemoizedFn } from 'ahooks';
import { Spin } from 'antd'; import { Spin } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
@@ -177,7 +178,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
}); });
}; };
const handleOnScroll = useCallback( const handleOnScroll = useMemoizedFn(
async (data: { isTop: boolean; isBottom: boolean }) => { async (data: { isTop: boolean; isBottom: boolean }) => {
const { isTop, isBottom } = data; const { isTop, isBottom } = data;
setIsAtTop(isTop); setIsAtTop(isTop);
@@ -225,17 +226,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
} else if (isBottom && page < totalPage) { } else if (isBottom && page < totalPage) {
// getNextPage(); // getNextPage();
} }
}, }
[
loading,
logs.length,
pageSize,
enableScorllLoad,
page,
totalPage,
setScrollPos,
createChunkConnection
]
); );
const debouncedScroll = useCallback( const debouncedScroll = useCallback(
+57 -41
View File
@@ -2,52 +2,68 @@ import IconFont from '@/components/icon-font';
import type { SelectProps } from 'antd'; import type { SelectProps } from 'antd';
import { Select } from 'antd'; import { Select } from 'antd';
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import styled from 'styled-components';
import NotFoundContent from '../components/not-found-content'; import NotFoundContent from '../components/not-found-content';
const BaseSelect: React.FC<SelectProps & { ref?: any }> = forwardRef( const Footer = styled.div`
(props, ref) => { color: var(--ant-color-text-tertiary);
const { notFoundContent, loading, ...restProps } = props; margin-top: 8px;
const [isFocus, setIsFocus] = React.useState(false); margin-bottom: 0;
const inputRef = React.useRef<any>(null); padding: 8px 12px;
border-top: 1px solid var(--ant-color-split);
`;
useImperativeHandle(ref, () => ({ const BaseSelect: React.FC<
...(inputRef.current || ({} as any)) SelectProps & { ref?: any; footer?: React.ReactNode }
})); > = forwardRef((props, ref) => {
const { notFoundContent, loading, ...restProps } = props;
const [isFocus, setIsFocus] = React.useState(false);
const inputRef = React.useRef<any>(null);
const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => { useImperativeHandle(ref, () => ({
setIsFocus(true); ...(inputRef.current || ({} as any))
props.onFocus?.(e); }));
};
const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => { const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {
setIsFocus(false); setIsFocus(true);
props.onBlur?.(e); props.onFocus?.(e);
}; };
const renderSuffixIcon = () => { const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
if (props.suffixIcon) { setIsFocus(false);
return props.suffixIcon; props.onBlur?.(e);
};
const renderSuffixIcon = () => {
if (props.suffixIcon) {
return props.suffixIcon;
}
if (!props.showSearch) {
return <IconFont type="icon-down"></IconFont>;
}
return !isFocus ? <IconFont type="icon-down"></IconFont> : undefined;
};
return (
<Select
{...restProps}
notFoundContent={
<NotFoundContent loading={loading} notFoundContent={notFoundContent} />
} }
if (!props.showSearch) { ref={inputRef}
return <IconFont type="icon-down"></IconFont>; onFocus={handleFocus}
onBlur={handleBlur}
suffixIcon={renderSuffixIcon()}
popupRender={
props.footer
? (originNode) => (
<>
{originNode}
{props.footer && <Footer>{props.footer}</Footer>}
</>
)
: undefined
} }
return !isFocus ? <IconFont type="icon-down"></IconFont> : undefined; />
}; );
});
return (
<Select
{...restProps}
notFoundContent={
<NotFoundContent
loading={loading}
notFoundContent={notFoundContent}
/>
}
ref={inputRef}
onFocus={handleFocus}
onBlur={handleBlur}
suffixIcon={renderSuffixIcon()}
/>
);
}
);
export default BaseSelect; export default BaseSelect;
+3 -1
View File
@@ -10,7 +10,9 @@ import { SealFormItemProps } from './types';
import Wrapper from './wrapper'; import Wrapper from './wrapper';
import SelectWrapper from './wrapper/select'; import SelectWrapper from './wrapper/select';
const SealSelect: React.FC<SelectProps & SealFormItemProps> = (props) => { const SealSelect: React.FC<
SelectProps & SealFormItemProps & { footer?: React.ReactNode }
> = (props) => {
const { const {
label, label,
placeholder, placeholder,
+2 -1
View File
@@ -267,5 +267,6 @@ export default {
'models.accessSettings.authed.tips': 'models.accessSettings.authed.tips':
'Accessible to all authenticated platform users.', 'Accessible to all authenticated platform users.',
'models.accessSettings.allowedUsers.tips': 'models.accessSettings.allowedUsers.tips':
'Only designated users can access the model.' 'Only designated users can access the model.',
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
}; };
+4 -2
View File
@@ -267,7 +267,8 @@ export default {
'models.accessSettings.authed.tips': 'models.accessSettings.authed.tips':
'Accessible to all authenticated platform users.', 'Accessible to all authenticated platform users.',
'models.accessSettings.allowedUsers.tips': 'models.accessSettings.allowedUsers.tips':
'Only designated users can access the model.' 'Only designated users can access the model.',
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -358,5 +359,6 @@ export default {
// 68. 'models.form.backendVersion.deprecated': 'Deprecated', // 68. 'models.form.backendVersion.deprecated': 'Deprecated',
// 69. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.', // 69. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.',
// 70. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.', // 70. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.',
// 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.' // 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.',
// 72. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+4 -2
View File
@@ -272,11 +272,13 @@ export default {
'models.accessSettings.authed.tips': 'models.accessSettings.authed.tips':
'Accessible to all authenticated platform users.', 'Accessible to all authenticated platform users.',
'models.accessSettings.allowedUsers.tips': 'models.accessSettings.allowedUsers.tips':
'Only designated users can access the model.' 'Only designated users can access the model.',
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.', // 1. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.',
// 2. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.', // 2. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.',
// 3. 'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.' // 3. 'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.',
// 4. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+2 -1
View File
@@ -251,5 +251,6 @@ export default {
'models.form.backendVersion.deprecated': '已弃用', 'models.form.backendVersion.deprecated': '已弃用',
'models.accessSettings.public.desc': '任何人无需认证即可访问。', 'models.accessSettings.public.desc': '任何人无需认证即可访问。',
'models.accessSettings.authed.tips': '平台内所有已认证用户可访问。', 'models.accessSettings.authed.tips': '平台内所有已认证用户可访问。',
'models.accessSettings.allowedUsers.tips': '仅允许选定的特定用户访问。' 'models.accessSettings.allowedUsers.tips': '仅允许选定的特定用户访问。',
'models.form.backendVersions.tips': `如需使用更多版本,请前往{link}页面并编辑对应的后端以添加版本。`
}; };
+6 -6
View File
@@ -152,37 +152,37 @@ export const frameworks = [
label: 'CANN', label: 'CANN',
value: GPUDriverMap.ASCEND, value: GPUDriverMap.ASCEND,
tips: ManufacturerMap[GPUDriverMap.ASCEND], tips: ManufacturerMap[GPUDriverMap.ASCEND],
locale: true tipLocale: true
}, },
{ {
label: 'DTK', label: 'DTK',
value: GPUDriverMap.HYGON, value: GPUDriverMap.HYGON,
tips: ManufacturerMap[GPUDriverMap.HYGON], tips: ManufacturerMap[GPUDriverMap.HYGON],
locale: true tipLocale: true
}, },
{ {
label: 'MACA', label: 'MACA',
value: GPUDriverMap.METAX, value: GPUDriverMap.METAX,
tips: ManufacturerMap[GPUDriverMap.METAX], tips: ManufacturerMap[GPUDriverMap.METAX],
locale: true tipLocale: true
}, },
{ {
label: 'CoreX', label: 'CoreX',
value: GPUDriverMap.ILUVATAR, value: GPUDriverMap.ILUVATAR,
tips: ManufacturerMap[GPUDriverMap.ILUVATAR], tips: ManufacturerMap[GPUDriverMap.ILUVATAR],
locale: true tipLocale: true
}, },
{ {
label: 'MUSA', label: 'MUSA',
value: GPUDriverMap.MOORE_THREADS, value: GPUDriverMap.MOORE_THREADS,
tips: ManufacturerMap[GPUDriverMap.MOORE_THREADS], tips: ManufacturerMap[GPUDriverMap.MOORE_THREADS],
locale: true tipLocale: true
}, },
{ {
label: 'Neuware', label: 'Neuware',
value: GPUDriverMap.CAMBRICON, value: GPUDriverMap.CAMBRICON,
tips: ManufacturerMap[GPUDriverMap.CAMBRICON], tips: ManufacturerMap[GPUDriverMap.CAMBRICON],
locale: true tipLocale: true
}, },
{ {
label: 'CPU', label: 'CPU',
+1 -1
View File
@@ -163,7 +163,7 @@ const VersionsForm: React.FC<AddModalProps> = ({
<span> <span>
{option.label} {option.label}
{data.tips ? ( {data.tips ? (
data.locale ? ( data.tipLocale ? (
<span className="text-tertiary m-l-4">{` [${intl.formatMessage({ id: data.tips })}]`}</span> <span className="text-tertiary m-l-4">{` [${intl.formatMessage({ id: data.tips })}]`}</span>
) : ( ) : (
<span className="text-tertiary m-l-4">{` [${data.tips}]`}</span> <span className="text-tertiary m-l-4">{` [${data.tips}]`}</span>
@@ -8,12 +8,24 @@ import { ExportOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form, Typography } from 'antd'; import { Form, Typography } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import styled from 'styled-components';
import { ProviderType, ProviderValueMap } from '../config'; import { ProviderType, ProviderValueMap } from '../config';
import { import {
CredentialFormData as FormData, CredentialFormData as FormData,
CredentialListItem as ListItem CredentialListItem as ListItem
} from '../config/types'; } from '../config/types';
const ExtraContent = styled.div`
display: flex;
justify-content: flex-end;
align-items: center;
position: absolute;
bottom: 6px;
right: 32px;
z-index: 1;
background: var(--ant-color-bg-container);
padding-inline: 8px;
`;
type AddModalProps = { type AddModalProps = {
title: string; title: string;
action: PageActionType; action: PageActionType;
@@ -105,18 +117,17 @@ const AddModal: React.FC<AddModalProps> = ({
id: 'clusters.credential.token' id: 'clusters.credential.token'
})} })}
required={action === PageAction.CREATE} required={action === PageAction.CREATE}
labelExtra={
<Typography.Link
href="https://cloud.digitalocean.com/account/api/tokens"
target="_blank"
rel="noopener noreferrer"
style={{ marginLeft: 8, lineHeight: 1 }}
>
{intl.formatMessage({ id: 'clusters.button.genToken' })}{' '}
<ExportOutlined style={{ fontSize: 12 }} />
</Typography.Link>
}
></SealInput.Password> ></SealInput.Password>
<ExtraContent>
<Typography.Link
href="https://cloud.digitalocean.com/account/api/tokens"
target="_blank"
rel="noopener noreferrer"
>
{intl.formatMessage({ id: 'clusters.button.genToken' })}{' '}
<ExportOutlined style={{ transform: 'scale(0.8)' }} />
</Typography.Link>
</ExtraContent>
</Form.Item> </Form.Item>
</> </>
)} )}
+24 -2
View File
@@ -1,8 +1,8 @@
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list'; import TooltipList from '@/components/tooltip-list';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { CaretDownOutlined } from '@ant-design/icons'; import { CaretDownOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
import { Form, Select } from 'antd'; import { Form, Select } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -24,6 +24,7 @@ const CaretDownWrapper = styled.span`
const BackendFields: React.FC = () => { const BackendFields: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const navigate = useNavigate();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { onValuesChange, backendOptions, onBackendChange } = useFormContext(); const { onValuesChange, backendOptions, onBackendChange } = useFormContext();
@@ -193,6 +194,27 @@ const BackendFields: React.FC = () => {
})} })}
onChange={handleBackendVersionOnChange} onChange={handleBackendVersionOnChange}
label={intl.formatMessage({ id: 'models.form.backendVersion' })} label={intl.formatMessage({ id: 'models.form.backendVersion' })}
footer={
<dl className="flex" style={{ marginBottom: 0 }}>
<dt>
<InfoCircleOutlined />
</dt>
<dd style={{ marginLeft: 8, marginBottom: 0 }}>
{intl.formatMessage(
{
id: 'models.form.backendVersions.tips'
},
{
link: (
<a onClick={() => navigate('/resources/backends')}>
{intl.formatMessage({ id: 'backends.title' })}
</a>
)
}
)}
</dd>
</dl>
}
> >
{renderVersionOptions( {renderVersionOptions(
backendVersions.builtIn, backendVersions.builtIn,
+2 -27
View File
@@ -1,16 +1,14 @@
import { clusterSessionAtom } from '@/atoms/clusters';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import { modelNameReg, PageAction } from '@/config'; import { modelNameReg } from '@/config';
import { GPUSTACK_API_BASE_URL } from '@/config/settings'; import { GPUSTACK_API_BASE_URL } from '@/config/settings';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { import {
ClusterStatusLabelMap, ClusterStatusLabelMap,
ClusterStatusValueMap ClusterStatusValueMap
} from '@/pages/cluster-management/config'; } from '@/pages/cluster-management/config';
import { Link, useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import { useAtom } from 'jotai';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { sourceOptions } from '../config'; import { sourceOptions } from '../config';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
@@ -30,26 +28,6 @@ interface BasicFormProps {
onSourceChange?: (value: string) => void; onSourceChange?: (value: string) => void;
} }
const NotFoundCredentialContent: React.FC = () => {
const [, setFromClusterCreation] = useAtom(clusterSessionAtom);
const intl = useIntl();
const handleOnClick = () => {
setFromClusterCreation({
firstAddWorker: false,
firstAddCluster: true
});
};
return (
<Link
to={`/cluster-management/clusters/create?action=${PageAction.CREATE}`}
onClick={handleOnClick}
>
{intl.formatMessage({ id: 'noresult.resources.gotocluster' })}
</Link>
);
};
const BasicForm: React.FC<BasicFormProps> = (props) => { const BasicForm: React.FC<BasicFormProps> = (props) => {
const { const {
fields = [], fields = [],
@@ -140,9 +118,6 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
> >
{ {
<SealSelect <SealSelect
notFoundContent={
<NotFoundCredentialContent></NotFoundCredentialContent>
}
onChange={handleClusterChange} onChange={handleClusterChange}
label={intl.formatMessage({ id: 'clusters.title' })} label={intl.formatMessage({ id: 'clusters.title' })}
options={clusterOptions} options={clusterOptions}