mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-22 00:42:47 +08:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -65,8 +65,10 @@ vi.mock('@ant-design/icons', async () => {
|
||||
const Icon = () => ReactModule.createElement('span', { 'data-icon': true });
|
||||
return {
|
||||
DeleteOutlined: Icon,
|
||||
DownOutlined: Icon,
|
||||
PlusOutlined: Icon,
|
||||
ReloadOutlined: Icon,
|
||||
RightOutlined: Icon,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -160,7 +162,7 @@ const instanceHealthSwitch = (renderer: ReactTestRenderer, endpoint: string) =>
|
||||
const instanceAction = (
|
||||
renderer: ReactTestRenderer,
|
||||
endpoint: string,
|
||||
action: 'edit' | 'deregister',
|
||||
action: 'toggle-details' | 'edit' | 'deregister',
|
||||
) => instanceRow(renderer, endpoint)?.find(
|
||||
(node) => node.type === 'button' && node.props['data-instance-action'] === action,
|
||||
);
|
||||
@@ -262,6 +264,11 @@ describe('NacosServiceViewer interactions', () => {
|
||||
enabled: true,
|
||||
ephemeral: false,
|
||||
clusterName: 'EDGE',
|
||||
metadata: {
|
||||
version: '20260728163719',
|
||||
slot: 'blue',
|
||||
activeConnections: '2',
|
||||
},
|
||||
}, {
|
||||
ip: '2001:db8::8',
|
||||
port: 8848,
|
||||
@@ -295,6 +302,11 @@ describe('NacosServiceViewer interactions', () => {
|
||||
|
||||
const row = instanceRow(renderer!, '10.0.0.8:8848');
|
||||
expect(row?.props.role).toBe('listitem');
|
||||
expect(row?.props['data-instance-details-expanded']).toBe('false');
|
||||
expect(
|
||||
row?.findByProps({ className: 'gn-nacos-instance-row__main' })
|
||||
.findAllByProps({ className: 'gn-nacos-instance-row__actions' }),
|
||||
).toHaveLength(1);
|
||||
expect(row?.findByProps({ className: 'gn-nacos-instance-row__endpoint' }).children)
|
||||
.toEqual(['10.0.0.8:8848']);
|
||||
expect(row?.findAllByType('dt').map((item) => item.children.join(''))).toEqual([
|
||||
@@ -302,10 +314,43 @@ describe('NacosServiceViewer interactions', () => {
|
||||
'Weight',
|
||||
'Type',
|
||||
]);
|
||||
expect(
|
||||
row?.findAllByProps({ className: 'gn-nacos-instance-row__metadata-panel' }),
|
||||
).toHaveLength(0);
|
||||
expect(
|
||||
instanceAction(renderer!, '10.0.0.8:8848', 'toggle-details')?.props['aria-expanded'],
|
||||
).toBe(false);
|
||||
|
||||
await act(async () => {
|
||||
instanceAction(renderer!, '10.0.0.8:8848', 'toggle-details')?.props.onClick();
|
||||
instanceAction(renderer!, '[2001:db8::8]:8848', 'toggle-details')?.props.onClick();
|
||||
});
|
||||
|
||||
const expandedRow = instanceRow(renderer!, '10.0.0.8:8848');
|
||||
expect(expandedRow?.props['data-instance-details-expanded']).toBe('true');
|
||||
expect(
|
||||
instanceAction(renderer!, '10.0.0.8:8848', 'toggle-details')?.props['aria-expanded'],
|
||||
).toBe(true);
|
||||
expect(
|
||||
expandedRow?.findByProps({ className: 'gn-nacos-instance-row__metadata-count' }).children,
|
||||
).toEqual(['3 entries']);
|
||||
expect(
|
||||
expandedRow?.findAllByProps({ className: 'gn-nacos-instance-row__metadata-key' })
|
||||
.map((item) => item.children.join('')),
|
||||
).toEqual(['activeConnections', 'slot', 'version']);
|
||||
expect(
|
||||
expandedRow?.findAllByProps({ className: 'gn-nacos-instance-row__metadata-value' })
|
||||
.map((item) => item.children.join('')),
|
||||
).toEqual(['2', 'blue', '20260728163719']);
|
||||
expect(
|
||||
instanceRow(renderer!, '[2001:db8::8]:8848')
|
||||
?.findByProps({ className: 'gn-nacos-instance-row__metadata-empty' }).children,
|
||||
).toEqual(['This instance has no metadata']);
|
||||
expect(instanceHealthSwitch(renderer!, '10.0.0.8:8848')?.props['aria-label'])
|
||||
.toContain('10.0.0.8:8848');
|
||||
expect(instanceAction(renderer!, '10.0.0.8:8848', 'edit')?.props['aria-label'])
|
||||
.toContain('10.0.0.8:8848');
|
||||
expect(instanceAction(renderer!, '10.0.0.8:8848', 'edit')?.props.type).toBe('text');
|
||||
expect(instanceAction(renderer!, '10.0.0.8:8848', 'deregister')?.props['aria-label'])
|
||||
.toContain('10.0.0.8:8848');
|
||||
});
|
||||
|
||||
@@ -15,8 +15,10 @@ import {
|
||||
} from 'antd';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
DownOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
RightOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import RedisResizableDivider from './RedisResizableDivider';
|
||||
import { buildRedisWorkbenchTheme } from './redisViewerWorkbenchTheme';
|
||||
@@ -103,6 +105,13 @@ const formatNacosInstanceEndpoint = (ip: string, port: number): string => {
|
||||
return `${displayHost}:${port}`;
|
||||
};
|
||||
|
||||
const getNacosInstanceMetadataEntries = (
|
||||
metadata?: Record<string, string>,
|
||||
): Array<[string, string]> => Object.entries(metadata || {})
|
||||
.filter(([key]) => key.trim().length > 0)
|
||||
.map(([key, value]): [string, string] => [key, String(value ?? '')])
|
||||
.sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
|
||||
|
||||
const NACOS_SERVICES_CHANGED_EVENT = 'gonavi:nacos-services-changed';
|
||||
|
||||
const NacosServiceViewer: React.FC<NacosServiceViewerProps> = ({
|
||||
@@ -176,6 +185,9 @@ const NacosServiceViewer: React.FC<NacosServiceViewerProps> = ({
|
||||
const [selectedServiceRaw, setSelectedServiceRaw] = useState<string | null>(null);
|
||||
const [selectedServiceDetail, setSelectedServiceDetail] = useState<NacosServiceDetail | null>(null);
|
||||
const [instances, setInstances] = useState<NacosInstance[]>([]);
|
||||
const [expandedInstanceKeys, setExpandedInstanceKeys] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
|
||||
const [serviceModalOpen, setServiceModalOpen] = useState(false);
|
||||
const [savingService, setSavingService] = useState(false);
|
||||
@@ -208,6 +220,22 @@ const NacosServiceViewer: React.FC<NacosServiceViewerProps> = ({
|
||||
() => (selectedServiceRaw ? parseNacosServiceName(selectedServiceRaw) : null),
|
||||
[selectedServiceRaw],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedInstanceKeys(new Set());
|
||||
}, [connectionId, namespaceId, selectedServiceRaw]);
|
||||
|
||||
const toggleInstanceDetails = useCallback((instanceKey: string) => {
|
||||
setExpandedInstanceKeys((current) => {
|
||||
const next = new Set(current);
|
||||
if (next.has(instanceKey)) {
|
||||
next.delete(instanceKey);
|
||||
} else {
|
||||
next.add(instanceKey);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
const serviceRows = useMemo<NacosServiceRow[]>(
|
||||
() => serviceNames.map((rawName) => ({ rawName, ...parseNacosServiceName(rawName) })),
|
||||
[serviceNames],
|
||||
@@ -1024,15 +1052,33 @@ const NacosServiceViewer: React.FC<NacosServiceViewerProps> = ({
|
||||
) : null}
|
||||
{instances.map((instance) => {
|
||||
const endpoint = formatNacosInstanceEndpoint(instance.ip, instance.port);
|
||||
const instanceKey = `${endpoint}:${instance.clusterName || ''}`;
|
||||
const detailsExpanded = expandedInstanceKeys.has(instanceKey);
|
||||
const metadataEntries = getNacosInstanceMetadataEntries(instance.metadata);
|
||||
return (
|
||||
<article
|
||||
key={`${endpoint}:${instance.clusterName || ''}`}
|
||||
key={instanceKey}
|
||||
className="gn-nacos-instance-row"
|
||||
data-instance-endpoint={endpoint}
|
||||
data-instance-details-expanded={detailsExpanded ? 'true' : 'false'}
|
||||
role="listitem"
|
||||
>
|
||||
<div className="gn-nacos-instance-row__main">
|
||||
<div className="gn-nacos-instance-row__identity">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="gn-nacos-instance-row__details-toggle"
|
||||
icon={detailsExpanded ? <DownOutlined /> : <RightOutlined />}
|
||||
data-instance-action="toggle-details"
|
||||
aria-expanded={detailsExpanded}
|
||||
aria-label={`${tr(
|
||||
detailsExpanded
|
||||
? 'nacos_service.action.collapse_instance_details'
|
||||
: 'nacos_service.action.expand_instance_details',
|
||||
)} ${endpoint}`}
|
||||
onClick={() => toggleInstanceDetails(instanceKey)}
|
||||
/>
|
||||
<span
|
||||
className={[
|
||||
'gn-nacos-instance-row__health-dot',
|
||||
@@ -1059,6 +1105,37 @@ const NacosServiceViewer: React.FC<NacosServiceViewerProps> = ({
|
||||
onChange={(checked) => void handleToggleHealth(instance, checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className="gn-nacos-instance-row__actions">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
data-instance-action="edit"
|
||||
aria-label={`${tr('nacos_service.action.edit_instance')} ${endpoint}`}
|
||||
disabled={dataEditRestricted}
|
||||
onClick={() => openEditInstance(instance)}
|
||||
>
|
||||
{tr('nacos_service.action.edit_instance')}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={tr('nacos_service.message.confirm_deregister', {
|
||||
ip: instance.ip,
|
||||
port: instance.port,
|
||||
})}
|
||||
disabled={dataEditRestricted}
|
||||
onConfirm={() => void handleDeregister(instance)}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
data-instance-action="deregister"
|
||||
aria-label={`${tr('nacos_service.action.deregister')} ${endpoint}`}
|
||||
disabled={dataEditRestricted}
|
||||
>
|
||||
{tr('nacos_service.action.deregister')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
<dl className="gn-nacos-instance-row__metadata">
|
||||
<div>
|
||||
<dt>{tr('nacos_service.field.cluster')}</dt>
|
||||
@@ -1077,35 +1154,45 @@ const NacosServiceViewer: React.FC<NacosServiceViewerProps> = ({
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div className="gn-nacos-instance-row__actions">
|
||||
<Button
|
||||
size="small"
|
||||
data-instance-action="edit"
|
||||
aria-label={`${tr('nacos_service.action.edit_instance')} ${endpoint}`}
|
||||
disabled={dataEditRestricted}
|
||||
onClick={() => openEditInstance(instance)}
|
||||
>
|
||||
{tr('nacos_service.action.edit_instance')}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={tr('nacos_service.message.confirm_deregister', {
|
||||
ip: instance.ip,
|
||||
port: instance.port,
|
||||
})}
|
||||
disabled={dataEditRestricted}
|
||||
onConfirm={() => void handleDeregister(instance)}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
data-instance-action="deregister"
|
||||
aria-label={`${tr('nacos_service.action.deregister')} ${endpoint}`}
|
||||
disabled={dataEditRestricted}
|
||||
{detailsExpanded ? (
|
||||
<section
|
||||
className="gn-nacos-instance-row__metadata-panel"
|
||||
aria-label={tr('nacos_service.field.instance_metadata')}
|
||||
>
|
||||
{tr('nacos_service.action.deregister')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<div className="gn-nacos-instance-row__metadata-header">
|
||||
<span>{tr('nacos_service.field.instance_metadata')}</span>
|
||||
<span className="gn-nacos-instance-row__metadata-count">
|
||||
{tr('nacos_service.field.metadata_count', {
|
||||
count: metadataEntries.length,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{metadataEntries.length > 0 ? (
|
||||
<ul className="gn-nacos-instance-row__metadata-list">
|
||||
{metadataEntries.map(([key, value]) => (
|
||||
<li key={key} className="gn-nacos-instance-row__metadata-item">
|
||||
<span
|
||||
className="gn-nacos-instance-row__metadata-key"
|
||||
title={key}
|
||||
>
|
||||
{key}
|
||||
</span>
|
||||
<span
|
||||
className="gn-nacos-instance-row__metadata-value"
|
||||
title={value}
|
||||
>
|
||||
{value || '—'}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<span className="gn-nacos-instance-row__metadata-empty">
|
||||
{tr('nacos_service.message.instance_metadata_empty')}
|
||||
</span>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,7 @@ const nacosBackend = vi.hoisted(() => ({
|
||||
NacosImportConfigs: vi.fn(),
|
||||
NacosDeleteConfig: vi.fn(),
|
||||
NacosGetConfig: vi.fn(),
|
||||
NacosPublishConfig: vi.fn(),
|
||||
NacosGetBetaConfig: vi.fn(),
|
||||
NacosStartConfigListen: vi.fn(),
|
||||
NacosStopConfigListen: vi.fn(),
|
||||
@@ -245,6 +246,7 @@ describe('NacosViewer config selection actions', () => {
|
||||
md5: 'test-md5',
|
||||
},
|
||||
});
|
||||
nacosBackend.NacosPublishConfig.mockResolvedValue({ success: true });
|
||||
nacosBackend.NacosGetBetaConfig.mockResolvedValue({
|
||||
success: true,
|
||||
data: { exists: false },
|
||||
@@ -775,6 +777,18 @@ describe('NacosViewer config selection actions', () => {
|
||||
expect(antdState.message.info).toHaveBeenCalledTimes(1);
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('Listening');
|
||||
|
||||
const compactBanner = renderer!.root.find(
|
||||
(node) => String(node.type) === 'alert',
|
||||
);
|
||||
expect(compactBanner.props.className).toBe('gn-v2-nacos-banner');
|
||||
expect(renderedText(compactBanner)).toContain('Remote config update detected');
|
||||
expect(renderedText(compactBanner)).toContain('Reload remote');
|
||||
expect(renderedText(compactBanner)).toContain('Dismiss');
|
||||
expect(renderedText(compactBanner)).not.toContain('Local draft is clean');
|
||||
expect(
|
||||
compactBanner.findByProps({ className: 'gn-v2-nacos-banner__copy' }),
|
||||
).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
findButtonByExactText(renderer!, 'Reload remote')!.props.onClick();
|
||||
});
|
||||
@@ -858,6 +872,135 @@ describe('NacosViewer config selection actions', () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
it('does not flash an internal dirty badge while publishing edited content', async () => {
|
||||
const pendingPublish = deferred<any>();
|
||||
nacosBackend.NacosPublishConfig.mockReturnValue(pendingPublish.promise);
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<NacosViewer connectionId="nacos-1" namespaceId="dev" namespaceName="dev" />,
|
||||
);
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
latestConfigTableProps().onRow(rows[0]).onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
renderer!.root.find(
|
||||
(node) => (node.type as any) === 'nacos-editor',
|
||||
).props.onChange('server: changed');
|
||||
});
|
||||
|
||||
const publish = findButtonByExactText(renderer!, 'Publish');
|
||||
expect(publish?.props.disabled).toBe(false);
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('dirty');
|
||||
|
||||
act(() => {
|
||||
publish!.props.onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('dirty');
|
||||
|
||||
pendingPublish.resolve({ success: true });
|
||||
await flushEffects();
|
||||
|
||||
expect(antdState.message.success).toHaveBeenCalledWith('Published successfully');
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('dirty');
|
||||
|
||||
await act(async () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not report its own publish event as a remote config change', async () => {
|
||||
const pendingListReload = deferred<any>();
|
||||
let pageListCalls = 0;
|
||||
nacosBackend.NacosSearchConfigs.mockImplementation(
|
||||
async (_config: unknown, query: any) => {
|
||||
if (query?.pageSize === 50) {
|
||||
pageListCalls += 1;
|
||||
if (pageListCalls === 2) return pendingListReload.promise;
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
totalCount: rows.length,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: rows,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
nacosBackend.NacosStartConfigListen.mockResolvedValue({
|
||||
success: true,
|
||||
data: { watchId: 'watch-publish' },
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<NacosViewer connectionId="nacos-1" namespaceId="dev" namespaceName="dev" />,
|
||||
);
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
latestConfigTableProps().onRow(rows[0]).onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
renderer!.root.find(
|
||||
(node) => (node.type as any) === 'nacos-editor',
|
||||
).props.onChange('server: published locally');
|
||||
});
|
||||
act(() => {
|
||||
findButtonByExactText(renderer!, 'Publish')!.props.onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
expect(antdState.message.success).toHaveBeenCalledWith('Published successfully');
|
||||
|
||||
await act(async () => {
|
||||
runtimeState.configChangedHandler!({
|
||||
watchId: 'watch-publish',
|
||||
connectionId: 'nacos-1',
|
||||
namespaceId: 'dev',
|
||||
group: rows[0].group,
|
||||
dataId: rows[0].dataId,
|
||||
});
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
const infoCallsDuringPublish = antdState.message.info.mock.calls.length;
|
||||
const remoteBannerDuringPublish = renderedText(renderer!.toJSON()).includes(
|
||||
'Remote config update detected',
|
||||
);
|
||||
|
||||
pendingListReload.resolve({
|
||||
success: true,
|
||||
data: {
|
||||
totalCount: rows.length,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: rows,
|
||||
},
|
||||
});
|
||||
await flushEffects();
|
||||
await act(async () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
expect(infoCallsDuringPublish).toBe(0);
|
||||
expect(remoteBannerDuringPublish).toBe(false);
|
||||
});
|
||||
|
||||
it('disables config import for restrictDataImport protection only', async () => {
|
||||
storeState.connections[0].config.protection.restrictDataImport = true;
|
||||
|
||||
|
||||
@@ -762,8 +762,12 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
message.warning(tr('nacos_viewer.message.beta_ips_required'));
|
||||
return;
|
||||
}
|
||||
const publishTarget = detail;
|
||||
const publishSelectionGeneration = selectionGenerationRef.current;
|
||||
let publishSucceeded = false;
|
||||
setPublishing(true);
|
||||
try {
|
||||
await stopListen();
|
||||
const res = await (window as any).go.app.App.NacosPublishConfig(rpcConfig, {
|
||||
namespaceId: namespaceId || '',
|
||||
dataId: detail.dataId,
|
||||
@@ -775,9 +779,11 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
betaIps: publishMode === 'beta' ? betaIps.trim() : '',
|
||||
});
|
||||
if (!res?.success) {
|
||||
void startListen(publishTarget, publishSelectionGeneration);
|
||||
message.error(res?.message || 'publish failed');
|
||||
return;
|
||||
}
|
||||
publishSucceeded = true;
|
||||
message.success(
|
||||
publishMode === 'beta'
|
||||
? tr('nacos_viewer.message.beta_publish_success')
|
||||
@@ -787,12 +793,15 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
setRemoteChanged(false);
|
||||
await loadList(pageNo);
|
||||
await loadDetail({
|
||||
dataId: detail.dataId,
|
||||
group: detail.group,
|
||||
dataId: publishTarget.dataId,
|
||||
group: publishTarget.group,
|
||||
type: draftType,
|
||||
});
|
||||
await loadBetaMeta({ dataId: detail.dataId, group: detail.group });
|
||||
await loadBetaMeta({ dataId: publishTarget.dataId, group: publishTarget.group });
|
||||
} catch (error: any) {
|
||||
if (!publishSucceeded) {
|
||||
void startListen(publishTarget, publishSelectionGeneration);
|
||||
}
|
||||
message.error(error?.message || String(error));
|
||||
} finally {
|
||||
setPublishing(false);
|
||||
@@ -1245,6 +1254,9 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
];
|
||||
|
||||
const namespaceLabel = namespaceName || (namespaceId ? namespaceId : 'public');
|
||||
const remoteChangedHint = draftDirty
|
||||
? tr('nacos_viewer.message.remote_changed_dirty_hint')
|
||||
: tr('nacos_viewer.message.remote_changed_clean_hint');
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1266,19 +1278,33 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
type="warning"
|
||||
showIcon
|
||||
className={isV2Ui ? 'gn-v2-nacos-banner' : undefined}
|
||||
style={isV2Ui ? { margin: 0, borderRadius: 0 } : undefined}
|
||||
message={tr('nacos_viewer.message.remote_changed_banner')}
|
||||
description={
|
||||
draftDirty
|
||||
? tr('nacos_viewer.message.remote_changed_dirty_hint')
|
||||
: tr('nacos_viewer.message.remote_changed_clean_hint')
|
||||
message={
|
||||
isV2Ui ? (
|
||||
<span className="gn-v2-nacos-banner__copy">
|
||||
<span className="gn-v2-nacos-banner__title">
|
||||
{tr('nacos_viewer.message.remote_changed_banner')}
|
||||
</span>
|
||||
{draftDirty ? (
|
||||
<span className="gn-v2-nacos-banner__hint" title={remoteChangedHint}>
|
||||
{remoteChangedHint}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : (
|
||||
tr('nacos_viewer.message.remote_changed_banner')
|
||||
)
|
||||
}
|
||||
description={isV2Ui ? undefined : remoteChangedHint}
|
||||
action={
|
||||
<Space>
|
||||
<Space size={4}>
|
||||
<Button size="small" type="primary" onClick={() => void handleReloadRemote()}>
|
||||
{tr('nacos_viewer.action.reload_remote')}
|
||||
</Button>
|
||||
<Button size="small" onClick={() => setRemoteChanged(false)}>
|
||||
<Button
|
||||
size="small"
|
||||
type={isV2Ui ? 'text' : 'default'}
|
||||
onClick={() => setRemoteChanged(false)}
|
||||
>
|
||||
{tr('nacos_viewer.action.dismiss_remote')}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -1608,7 +1634,6 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
<Tag>{detail.group}</Tag>
|
||||
<strong style={{ color: workbenchTheme.textPrimary }}>{detail.dataId}</strong>
|
||||
{detail.md5 ? <Tag color="default">{detail.md5}</Tag> : null}
|
||||
{draftDirty ? <Tag color="orange">dirty</Tag> : null}
|
||||
{betaExists ? <Tag color="purple">{tr('nacos_viewer.status.beta_active')}</Tag> : null}
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -908,10 +908,83 @@ body[data-ui-version="v2"] .gn-v2-nacos-workbench {
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner {
|
||||
border-radius: 0 !important;
|
||||
border-left: 0 !important;
|
||||
border-right: 0 !important;
|
||||
border-top: 0 !important;
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
width: min(820px, calc(100% - 24px));
|
||||
min-height: 40px;
|
||||
margin: 8px auto !important;
|
||||
padding: 6px 8px 6px 10px !important;
|
||||
align-items: center !important;
|
||||
border: 1px solid color-mix(in srgb, var(--gn-warn) 28%, transparent) !important;
|
||||
border-inline-start: 3px solid var(--gn-warn) !important;
|
||||
border-radius: 8px !important;
|
||||
background: color-mix(in srgb, var(--gn-warn-soft) 72%, var(--gn-bg-panel)) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner .ant-alert-icon {
|
||||
margin-inline-end: 9px;
|
||||
color: var(--gn-warn) !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner .ant-alert-content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner .ant-alert-message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner__copy {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner__title {
|
||||
flex: 0 0 auto;
|
||||
color: var(--gn-fg-1);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner__hint {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--gn-fg-3);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner .ant-alert-action {
|
||||
margin-inline-start: 12px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner .ant-btn {
|
||||
height: 26px;
|
||||
padding-inline: 9px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner {
|
||||
width: calc(100% - 12px);
|
||||
margin: 6px !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner__copy {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-nacos-banner__hint {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1459,28 +1532,27 @@ body[data-ui-version="v2"] .gn-v2-nacos-detail-pane .ant-table-tbody > tr.ant-ta
|
||||
|
||||
.gn-nacos-instance-row {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
column-gap: 20px;
|
||||
min-width: 0;
|
||||
padding: 16px 4px 16px 8px;
|
||||
border-bottom: 1px solid var(--gn-br-1, rgba(15, 23, 42, 0.08));
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row:first-child {
|
||||
border-top: 1px solid var(--gn-br-1, rgba(15, 23, 42, 0.08));
|
||||
.gn-nacos-instance-row + .gn-nacos-instance-row {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__main {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 13px 20px;
|
||||
grid-template-columns: minmax(240px, 1.2fr) minmax(300px, 1fr) auto auto;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__identity {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
min-width: 0;
|
||||
min-height: 52px;
|
||||
padding: 9px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
@@ -1491,6 +1563,20 @@ body[data-ui-version="v2"] .gn-v2-nacos-detail-pane .ant-table-tbody > tr.ant-ta
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__details-toggle.ant-btn {
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
flex: 0 0 24px;
|
||||
margin-inline-start: -4px;
|
||||
padding: 0;
|
||||
color: var(--gn-fg-4, inherit);
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__details-toggle.ant-btn .anticon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__health-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
@@ -1522,6 +1608,10 @@ body[data-ui-version="v2"] .gn-v2-nacos-detail-pane .ant-table-tbody > tr.ant-ta
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__health-control {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
min-height: 52px;
|
||||
padding: 9px 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
@@ -1532,20 +1622,21 @@ body[data-ui-version="v2"] .gn-v2-nacos-detail-pane .ant-table-tbody > tr.ant-ta
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata {
|
||||
grid-column: 1 / -1;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 7px 14px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(88px, 1fr));
|
||||
gap: 8px 18px;
|
||||
grid-template-columns: repeat(3, minmax(72px, 1fr));
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata > div {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata dt {
|
||||
@@ -1566,17 +1657,102 @@ body[data-ui-version="v2"] .gn-v2-nacos-detail-pane .ant-table-tbody > tr.ant-ta
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__actions {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-inline-start: 16px;
|
||||
border-inline-start: 1px solid var(--gn-br-1, rgba(15, 23, 42, 0.08));
|
||||
.gn-nacos-instance-row__metadata-panel {
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-nacos-instance-row:hover {
|
||||
background: color-mix(in srgb, var(--gn-bg-hover) 72%, transparent);
|
||||
.gn-nacos-instance-row__metadata-header {
|
||||
min-height: 30px;
|
||||
padding: 6px 10px 6px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--gn-br-1, rgba(15, 23, 42, 0.08));
|
||||
color: var(--gn-fg-3, inherit);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-count {
|
||||
flex: 0 0 auto;
|
||||
color: var(--gn-fg-4, inherit);
|
||||
font-family: var(--gn-font-mono, ui-monospace, monospace);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
minmax(150px, 0.52fr) minmax(170px, 1fr)
|
||||
minmax(150px, 0.52fr) minmax(170px, 1fr);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-item {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-key,
|
||||
.gn-nacos-instance-row__metadata-value {
|
||||
min-width: 0;
|
||||
min-height: 36px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--gn-br-1, rgba(15, 23, 42, 0.08)) 72%, transparent);
|
||||
font-family: var(--gn-font-mono, ui-monospace, monospace);
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-key {
|
||||
overflow: hidden;
|
||||
color: var(--gn-fg-4, inherit);
|
||||
background: color-mix(in srgb, var(--gn-bg-panel, transparent) 48%, transparent);
|
||||
border-inline-end: 1px solid color-mix(in srgb, var(--gn-br-1, rgba(15, 23, 42, 0.08)) 72%, transparent);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-item:nth-child(even) .gn-nacos-instance-row__metadata-key {
|
||||
border-inline-start: 1px solid color-mix(in srgb, var(--gn-br-1, rgba(15, 23, 42, 0.08)) 72%, transparent);
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-value {
|
||||
color: var(--gn-fg-2, inherit);
|
||||
font-weight: 550;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-empty {
|
||||
min-height: 42px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--gn-fg-4, inherit);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__actions {
|
||||
grid-column: 4;
|
||||
grid-row: 1;
|
||||
min-height: 52px;
|
||||
padding: 8px 4px 8px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__actions .ant-btn {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-nacos-service-table .ant-table-tbody > tr {
|
||||
@@ -1587,32 +1763,66 @@ body[data-ui-version="v2"] .gn-nacos-service-list-footer {
|
||||
color: var(--gn-fg-4);
|
||||
}
|
||||
|
||||
@container gn-nacos-instance-inspector (max-width: 620px) {
|
||||
.gn-nacos-instance-row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
row-gap: 14px;
|
||||
@container gn-nacos-instance-inspector (max-width: 860px) {
|
||||
.gn-nacos-instance-row__main {
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__health-control {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__actions {
|
||||
min-height: 34px;
|
||||
justify-content: flex-end;
|
||||
padding: 12px 0 0;
|
||||
grid-column: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@container gn-nacos-instance-inspector (max-width: 760px) {
|
||||
.gn-nacos-instance-row__metadata-list {
|
||||
grid-template-columns: minmax(128px, 0.42fr) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata-item:nth-child(even) .gn-nacos-instance-row__metadata-key {
|
||||
border-inline-start: 0;
|
||||
border-top: 1px solid var(--gn-br-1, rgba(15, 23, 42, 0.08));
|
||||
}
|
||||
}
|
||||
|
||||
@container gn-nacos-instance-inspector (max-width: 620px) {
|
||||
.gn-nacos-instance-row__main {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__identity {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__health-control {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
min-height: 42px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__actions {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
min-height: 42px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
@container gn-nacos-instance-inspector (max-width: 440px) {
|
||||
.gn-nacos-instance-row__main {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__health-control {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.gn-nacos-instance-row__metadata {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-columns: repeat(3, minmax(64px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user