B4 frontend: probe task management modal on the nodes page
- ProbeTasksModal: self-loading/self-saving surface (B3 probe-meta pattern), tasks grouped by region label (ungrouped last), editor with name/region/target/interval/auto-join + agent-node multi select snapshotting baseNodeIds for the base-diff save; delete with confirm. Toolbar entry on NodeList (RadarChart icon), wired in NodesPage; NodeList call sites in two component tests updated for the new required prop. - i18n pages.nodes.probeTasks.* (25 keys) in all 13 locales: zh-CN translated, en-US English, others English placeholder per repo convention; insertion verified pure-additive (351 insertions, 0 deletions) and the i18n dead-key failures are byte-identical to the B3 baseline. - tsc clean; oxlint 0/0 (baseline parity); oxfmt clean on touched files (3 pre-existing drift files untouched).
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
InfoCircleOutlined,
|
||||
MoreOutlined,
|
||||
PlusOutlined,
|
||||
RadarChartOutlined,
|
||||
RightOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
TeamOutlined,
|
||||
@@ -48,6 +49,7 @@ interface NodeListProps {
|
||||
onSelectionChange: (ids: number[]) => void;
|
||||
onAdd: () => void;
|
||||
onMtls: () => void;
|
||||
onProbeTasks: () => void;
|
||||
onEdit: (node: NodeRecord) => void;
|
||||
onDelete: (node: NodeRecord) => void;
|
||||
onProbe: (node: NodeRecord) => void;
|
||||
@@ -218,6 +220,7 @@ export default function NodeList({
|
||||
onSelectionChange,
|
||||
onAdd,
|
||||
onMtls,
|
||||
onProbeTasks,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onProbe,
|
||||
@@ -618,6 +621,9 @@ export default function NodeList({
|
||||
<Button icon={<SafetyCertificateOutlined />} onClick={onMtls}>
|
||||
{t('pages.nodes.mtls.title')}
|
||||
</Button>
|
||||
<Button icon={<RadarChartOutlined />} onClick={onProbeTasks}>
|
||||
{t('pages.nodes.probeTasks.title')}
|
||||
</Button>
|
||||
{selectedIds.length > 0 && (
|
||||
<Button icon={<CloudDownloadOutlined />} onClick={onUpdateSelected}>
|
||||
{t('pages.nodes.updateSelected', { count: selectedIds.length })}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { useNodeMutations } from '@/api/queries/useNodeMutations';
|
||||
import AppSidebar from '@/layouts/AppSidebar';
|
||||
import NodeList from './NodeList';
|
||||
import NodeFormModal from './NodeFormModal';
|
||||
import ProbeTasksModal from './ProbeTasksModal';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
import { HttpUtil } from '@/utils';
|
||||
import type { PanelUpdateInfo } from '../index/PanelUpdateModal';
|
||||
@@ -104,6 +105,7 @@ export default function NodesPage() {
|
||||
const [formNode, setFormNode] = useState<NodeRecord | null>(null);
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const [mtlsOpen, setMtlsOpen] = useState(false);
|
||||
const [probeTasksOpen, setProbeTasksOpen] = useState(false);
|
||||
const [trustCa, setTrustCa] = useState('');
|
||||
const [copyingCa, setCopyingCa] = useState(false);
|
||||
const [savingTrustCa, setSavingTrustCa] = useState(false);
|
||||
@@ -367,6 +369,7 @@ export default function NodesPage() {
|
||||
onSelectionChange={setSelectedIds}
|
||||
onAdd={onAdd}
|
||||
onMtls={() => setMtlsOpen(true)}
|
||||
onProbeTasks={() => setProbeTasksOpen(true)}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onProbe={onProbe}
|
||||
@@ -381,6 +384,12 @@ export default function NodesPage() {
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
|
||||
<ProbeTasksModal
|
||||
open={probeTasksOpen}
|
||||
onClose={() => setProbeTasksOpen(false)}
|
||||
nodes={nodes}
|
||||
/>
|
||||
|
||||
<NodeFormModal
|
||||
open={formOpen}
|
||||
mode={formMode}
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { HttpUtil } from '@/utils';
|
||||
import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
||||
|
||||
// Item 7 B4: probe (latency) task management. The operator maintains
|
||||
// the target set here — grouped by the free-form region label, no
|
||||
// preset list (user ruling Q4). Like ProbeMetaSection the surface is
|
||||
// self-contained: it loads and saves on its own through the dedicated
|
||||
// endpoints, effective immediately (the master pushes the set on each
|
||||
// agent's next config poll; nothing restarts).
|
||||
interface ProbeTaskWire {
|
||||
id: number;
|
||||
name: string;
|
||||
target: string;
|
||||
interval: number;
|
||||
autoJoin: boolean;
|
||||
sort: number;
|
||||
region: string;
|
||||
nodeIds: number[];
|
||||
}
|
||||
|
||||
interface TaskDraft {
|
||||
id: number; // 0 = new task
|
||||
name: string;
|
||||
target: string;
|
||||
interval: number;
|
||||
autoJoin: boolean;
|
||||
region: string;
|
||||
nodeIds: number[];
|
||||
baseNodeIds: number[] | null; // editor-open snapshot (existing tasks)
|
||||
}
|
||||
|
||||
const TASKS_URL = '/panel/api/agent/probe-tasks';
|
||||
|
||||
export default function ProbeTasksModal({
|
||||
open,
|
||||
onClose,
|
||||
nodes,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
nodes: NodeRecord[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [messageApi, messageContextHolder] = message.useMessage();
|
||||
const [tasks, setTasks] = useState<ProbeTaskWire[] | null>(null);
|
||||
const [draft, setDraft] = useState<TaskDraft | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const agentNodes = useMemo(() => nodes.filter((n) => n.nodeType === 'agent'), [nodes]);
|
||||
const nodeName = useCallback(
|
||||
(id: number) => nodes.find((n) => n.id === id)?.name ?? `#${id}`,
|
||||
[nodes],
|
||||
);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const msg = await HttpUtil.get<ProbeTaskWire[]>(TASKS_URL, { silent: true });
|
||||
if (msg?.success && msg.obj) {
|
||||
setTasks(msg.obj);
|
||||
} else {
|
||||
messageApi.error(msg?.msg || t('pages.nodes.probeTasks.loadFailed'));
|
||||
}
|
||||
}, [messageApi, t]);
|
||||
|
||||
// Load on open (afterOpenChange is an event callback, so resetting
|
||||
// the previous session's state here is not an effect write).
|
||||
const onAfterOpenChange = (opened: boolean) => {
|
||||
if (opened) {
|
||||
setTasks(null);
|
||||
setDraft(null);
|
||||
void load();
|
||||
}
|
||||
};
|
||||
|
||||
// Group by region: region labels ascending, the ungrouped bucket
|
||||
// last; inside a group the panel order (sort) rules.
|
||||
const groups = useMemo(() => {
|
||||
const byRegion = new Map<string, ProbeTaskWire[]>();
|
||||
for (const task of tasks ?? []) {
|
||||
const key = task.region ?? '';
|
||||
const list = byRegion.get(key) ?? [];
|
||||
list.push(task);
|
||||
byRegion.set(key, list);
|
||||
}
|
||||
return [...byRegion.entries()]
|
||||
.sort(([a], [b]) => {
|
||||
if (a === b) return 0;
|
||||
if (a === '') return 1;
|
||||
if (b === '') return -1;
|
||||
return a < b ? -1 : 1;
|
||||
})
|
||||
.map(([region, list]) => ({ region, list }));
|
||||
}, [tasks]);
|
||||
|
||||
const startCreate = () =>
|
||||
setDraft({
|
||||
id: 0,
|
||||
name: '',
|
||||
target: '',
|
||||
interval: 60,
|
||||
autoJoin: false,
|
||||
region: '',
|
||||
nodeIds: [],
|
||||
baseNodeIds: null,
|
||||
});
|
||||
|
||||
const startEdit = (task: ProbeTaskWire) =>
|
||||
setDraft({
|
||||
id: task.id,
|
||||
name: task.name,
|
||||
target: task.target,
|
||||
interval: task.interval,
|
||||
autoJoin: task.autoJoin,
|
||||
region: task.region ?? '',
|
||||
nodeIds: [...task.nodeIds],
|
||||
baseNodeIds: [...task.nodeIds],
|
||||
});
|
||||
|
||||
async function onSave() {
|
||||
if (!draft) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
id: draft.id,
|
||||
name: draft.name,
|
||||
target: draft.target,
|
||||
interval: draft.interval,
|
||||
autoJoin: draft.autoJoin,
|
||||
region: draft.region,
|
||||
nodeIds: draft.nodeIds,
|
||||
};
|
||||
if (draft.baseNodeIds) payload.baseNodeIds = draft.baseNodeIds;
|
||||
const msg = await HttpUtil.put<{ id: number }>(TASKS_URL, payload, { silent: true });
|
||||
if (msg?.success) {
|
||||
messageApi.success(t('pages.nodes.probeTasks.saved'));
|
||||
setDraft(null);
|
||||
await load();
|
||||
} else {
|
||||
messageApi.error(msg?.msg || t('pages.nodes.probeTasks.saveFailed'));
|
||||
}
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete(task: ProbeTaskWire) {
|
||||
const msg = await HttpUtil.delete(`${TASKS_URL}/${task.id}`, { silent: true });
|
||||
if (msg?.success) {
|
||||
messageApi.success(t('pages.nodes.probeTasks.deleted'));
|
||||
if (draft?.id === task.id) setDraft(null);
|
||||
await load();
|
||||
} else {
|
||||
messageApi.error(msg?.msg || t('pages.nodes.probeTasks.saveFailed'));
|
||||
}
|
||||
}
|
||||
|
||||
const columns: ColumnsType<ProbeTaskWire> = [
|
||||
{ title: t('pages.nodes.probeTasks.name'), dataIndex: 'name', key: 'name' },
|
||||
{
|
||||
title: t('pages.nodes.probeTasks.target'),
|
||||
dataIndex: 'target',
|
||||
key: 'target',
|
||||
render: (v: string) => <Typography.Text code>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: t('pages.nodes.probeTasks.interval'),
|
||||
dataIndex: 'interval',
|
||||
key: 'interval',
|
||||
width: 90,
|
||||
render: (v: number) => `${v}s`,
|
||||
},
|
||||
{
|
||||
title: t('pages.nodes.probeTasks.autoJoin'),
|
||||
dataIndex: 'autoJoin',
|
||||
key: 'autoJoin',
|
||||
width: 90,
|
||||
render: (v: boolean) =>
|
||||
v ? <Tag color="blue">{t('pages.nodes.probeTasks.autoJoinOn')}</Tag> : null,
|
||||
},
|
||||
{
|
||||
title: t('pages.nodes.probeTasks.nodes'),
|
||||
dataIndex: 'nodeIds',
|
||||
key: 'nodeIds',
|
||||
render: (ids: number[]) => <span title={ids.map(nodeName).join(', ')}>{ids.length}</span>,
|
||||
},
|
||||
{
|
||||
title: t('pages.nodes.probeTasks.actions'),
|
||||
key: 'actions',
|
||||
width: 110,
|
||||
render: (_, task) => (
|
||||
<Space size="small">
|
||||
<Button size="small" icon={<EditOutlined />} onClick={() => startEdit(task)}>
|
||||
{t('pages.nodes.probeTasks.edit')}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={t('pages.nodes.probeTasks.deleteConfirm')}
|
||||
onConfirm={() => void onDelete(task)}
|
||||
okText={t('pages.nodes.probeTasks.delete')}
|
||||
cancelText={t('pages.nodes.probeTasks.cancel')}
|
||||
>
|
||||
<Button size="small" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const setDraftField = <K extends keyof TaskDraft>(key: K, value: TaskDraft[K]) =>
|
||||
setDraft((d) => (d ? { ...d, [key]: value } : d));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={t('pages.nodes.probeTasks.title')}
|
||||
onCancel={onClose}
|
||||
afterOpenChange={onAfterOpenChange}
|
||||
footer={null}
|
||||
width={780}
|
||||
destroyOnHidden
|
||||
>
|
||||
{messageContextHolder}
|
||||
<Typography.Paragraph type="secondary" style={{ marginTop: 0 }}>
|
||||
{t('pages.nodes.probeTasks.intro')}
|
||||
</Typography.Paragraph>
|
||||
|
||||
{tasks === null ? (
|
||||
<Typography.Text type="secondary">{t('pages.nodes.probeTasks.loading')}</Typography.Text>
|
||||
) : tasks.length === 0 && !draft ? (
|
||||
<Typography.Text type="secondary">{t('pages.nodes.probeTasks.empty')}</Typography.Text>
|
||||
) : (
|
||||
groups.map(({ region, list }) => (
|
||||
<div key={region || '__ungrouped'} style={{ marginBottom: 16 }}>
|
||||
<Typography.Text strong>
|
||||
{region || t('pages.nodes.probeTasks.ungrouped')}
|
||||
</Typography.Text>
|
||||
<Table<ProbeTaskWire>
|
||||
size="small"
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={list}
|
||||
pagination={false}
|
||||
style={{ marginTop: 6 }}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
{draft ? (
|
||||
<div
|
||||
style={{
|
||||
borderTop: '1px solid var(--ant-color-border-secondary, #f0f0f0)',
|
||||
paddingTop: 12,
|
||||
marginTop: 4,
|
||||
}}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<Space wrap>
|
||||
<Input
|
||||
placeholder={t('pages.nodes.probeTasks.name')}
|
||||
value={draft.name}
|
||||
onChange={(e) => setDraftField('name', e.target.value)}
|
||||
style={{ width: 180 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('pages.nodes.probeTasks.region')}
|
||||
value={draft.region}
|
||||
onChange={(e) => setDraftField('region', e.target.value)}
|
||||
style={{ width: 130 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('pages.nodes.probeTasks.targetPlaceholder')}
|
||||
value={draft.target}
|
||||
onChange={(e) => setDraftField('target', e.target.value)}
|
||||
style={{ width: 230, fontFamily: 'monospace' }}
|
||||
/>
|
||||
<InputNumber
|
||||
min={5}
|
||||
max={3600}
|
||||
value={draft.interval}
|
||||
onChange={(v) => setDraftField('interval', v ?? 60)}
|
||||
addonAfter="s"
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>
|
||||
{t('pages.nodes.probeTasks.autoJoin')}{' '}
|
||||
<Switch checked={draft.autoJoin} onChange={(v) => setDraftField('autoJoin', v)} />
|
||||
</span>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={t('pages.nodes.probeTasks.nodesPlaceholder')}
|
||||
value={draft.nodeIds}
|
||||
onChange={(v) => setDraftField('nodeIds', v)}
|
||||
options={agentNodes.map((n) => ({ value: n.id, label: n.name }))}
|
||||
style={{ minWidth: 260 }}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button type="primary" loading={saving} onClick={() => void onSave()}>
|
||||
{t('pages.nodes.probeTasks.save')}
|
||||
</Button>
|
||||
<Button onClick={() => setDraft(null)}>{t('pages.nodes.probeTasks.cancel')}</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
) : (
|
||||
<Button icon={<PlusOutlined />} onClick={startCreate} style={{ marginTop: 8 }}>
|
||||
{t('pages.nodes.probeTasks.add')}
|
||||
</Button>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ function renderNodes(nodes: NodeRecord[]) {
|
||||
onSelectionChange={noop}
|
||||
onAdd={noop}
|
||||
onMtls={noop}
|
||||
onProbeTasks={noop}
|
||||
onEdit={noop}
|
||||
onDelete={noop}
|
||||
onProbe={noop}
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('NodeList desktop table row keys', () => {
|
||||
onSelectionChange={noop}
|
||||
onAdd={noop}
|
||||
onMtls={noop}
|
||||
onProbeTasks={noop}
|
||||
onEdit={noop}
|
||||
onDelete={noop}
|
||||
onProbe={noop}
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1099,6 +1099,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -981,6 +981,33 @@
|
||||
"save": "保存状态页信息",
|
||||
"saved": "状态页信息已保存",
|
||||
"saveFailed": "状态页信息保存失败"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "延迟探测",
|
||||
"intro": "由 agent 节点执行的 TCP 延迟探测。修改在各 agent 下一次拉取配置时生效(约 30 秒),无需重启。",
|
||||
"loading": "加载中…",
|
||||
"empty": "还没有探测任务,新增一个开始测延迟。",
|
||||
"add": "新增探测",
|
||||
"name": "名称",
|
||||
"target": "目标",
|
||||
"targetPlaceholder": "主机:端口,如 1.1.1.1:443",
|
||||
"interval": "间隔(秒)",
|
||||
"region": "地区",
|
||||
"ungrouped": "未分组",
|
||||
"autoJoin": "自动加入",
|
||||
"autoJoinOn": "自动",
|
||||
"nodes": "指派节点",
|
||||
"nodesPlaceholder": "选择节点",
|
||||
"actions": "操作",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"deleteConfirm": "删除这个探测及其历史数据?",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"saved": "探测任务已保存",
|
||||
"deleted": "探测任务已删除",
|
||||
"loadFailed": "探测任务加载失败",
|
||||
"saveFailed": "操作失败"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -959,6 +959,33 @@
|
||||
"save": "Save status page info",
|
||||
"saved": "Status page info saved",
|
||||
"saveFailed": "Saving status page info failed"
|
||||
},
|
||||
"probeTasks": {
|
||||
"title": "Probe tasks",
|
||||
"intro": "TCP latency probes run by agent nodes. Changes apply on each agent's next config poll (about 30 s); nothing restarts.",
|
||||
"loading": "Loading…",
|
||||
"empty": "No probe tasks yet. Add one to start measuring latency.",
|
||||
"add": "Add probe",
|
||||
"name": "Name",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "host:port, e.g. 1.1.1.1:443",
|
||||
"interval": "Interval (s)",
|
||||
"region": "Region",
|
||||
"ungrouped": "Ungrouped",
|
||||
"autoJoin": "Auto-join",
|
||||
"autoJoinOn": "Auto",
|
||||
"nodes": "Nodes",
|
||||
"nodesPlaceholder": "Assign nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete this probe and its history?",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Probe task saved",
|
||||
"deleted": "Probe task deleted",
|
||||
"loadFailed": "Failed to load probe tasks",
|
||||
"saveFailed": "Operation failed"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
Reference in New Issue
Block a user