probe: latency add row always visible, single-line with select-all nodes
- Drop the '+ add probe' toggle button; the add/edit form is now always shown, resetting to a blank add state after save/cancel. - Single nowrap row: name, host:port (flex 6), interval (flex 1), auto-join switch, node multi-select (flex 2), select-all-nodes button (disabled with no agent nodes), save/cancel. - Remove the region input from the task editor (region value is still kept in the draft and in the save payload, so editing an existing task never wipes its region; grouping display unchanged). - i18n: drop probeTasks.add / probeTasks.region (13 locales), add probeTasks.selectAllNodes (13 locales, key sets in sync).
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
Typography,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { HttpUtil } from '@/utils';
|
||||
import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
||||
@@ -55,7 +55,20 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
const { t } = useTranslation();
|
||||
const [messageApi, messageContextHolder] = message.useMessage();
|
||||
const [tasks, setTasks] = useState<ProbeTaskWire[] | null>(null);
|
||||
const [draft, setDraft] = useState<TaskDraft | null>(null);
|
||||
// The add row is always visible (no expand button): a blank draft
|
||||
// is the resting state, and editing a row loads that task into the
|
||||
// same row (cancel / a successful save resets it to blank).
|
||||
const blankDraft = (): TaskDraft => ({
|
||||
id: 0,
|
||||
name: '',
|
||||
target: '',
|
||||
interval: 60,
|
||||
autoJoin: false,
|
||||
region: '',
|
||||
nodeIds: [],
|
||||
baseNodeIds: null,
|
||||
});
|
||||
const [draft, setDraft] = useState<TaskDraft>(blankDraft);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const agentNodes = useMemo(() => nodes.filter((n) => n.nodeType === 'agent'), [nodes]);
|
||||
@@ -97,18 +110,6 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
.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,
|
||||
@@ -122,7 +123,6 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
});
|
||||
|
||||
async function onSave() {
|
||||
if (!draft) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
@@ -141,7 +141,7 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
});
|
||||
if (msg?.success) {
|
||||
messageApi.success(t('pages.nodes.probeTasks.saved'));
|
||||
setDraft(null);
|
||||
setDraft(blankDraft());
|
||||
await load();
|
||||
} else {
|
||||
messageApi.error(msg?.msg || t('pages.nodes.probeTasks.saveFailed'));
|
||||
@@ -155,7 +155,7 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
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);
|
||||
if (draft.id === task.id) setDraft(blankDraft());
|
||||
await load();
|
||||
} else {
|
||||
messageApi.error(msg?.msg || t('pages.nodes.probeTasks.saveFailed'));
|
||||
@@ -225,7 +225,7 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
|
||||
{tasks === null ? (
|
||||
<Typography.Text type="secondary">{t('pages.nodes.probeTasks.loading')}</Typography.Text>
|
||||
) : tasks.length === 0 && !draft ? (
|
||||
) : tasks.length === 0 ? (
|
||||
<Typography.Text type="secondary">{t('pages.nodes.probeTasks.empty')}</Typography.Text>
|
||||
) : (
|
||||
groups.map(({ region, list }) => (
|
||||
@@ -245,70 +245,79 @@ export default function ProbeTasksPanel({ nodes }: { nodes: NodeRecord[] }) {
|
||||
))
|
||||
)}
|
||||
|
||||
{draft ? (
|
||||
<div
|
||||
style={{
|
||||
borderTop: '1px solid var(--ant-color-border-secondary, #f0f0f0)',
|
||||
paddingTop: 12,
|
||||
marginTop: 4,
|
||||
}}
|
||||
{/* Always-visible single-line add row (no expand button): the
|
||||
name field the redesign did not mention is kept, first, so
|
||||
the specified order of the other elements is unchanged.
|
||||
The region field is gone from the row; the value itself
|
||||
still rides along in the payload, so editing an existing
|
||||
task keeps its region (new tasks get ''). Widths are flex
|
||||
ratios: target 6 : interval 1, matching the specified
|
||||
1.5x target (230 -> ~345) against 0.5x interval (120 -> 60)
|
||||
relationship (345:60 ~ 5.75:1, rounded to 6:1). */}
|
||||
<div
|
||||
style={{
|
||||
borderTop: '1px solid var(--ant-color-border-secondary, #f0f0f0)',
|
||||
paddingTop: 12,
|
||||
marginTop: 4,
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder={t('pages.nodes.probeTasks.name')}
|
||||
value={draft.name}
|
||||
onChange={(e) => setDraftField('name', e.target.value)}
|
||||
style={{ flex: '2 1 0', minWidth: 0 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('pages.nodes.probeTasks.targetPlaceholder')}
|
||||
value={draft.target}
|
||||
onChange={(e) => setDraftField('target', e.target.value)}
|
||||
style={{ flex: '6 1 0', minWidth: 0, fontFamily: 'monospace' }}
|
||||
/>
|
||||
<InputNumber
|
||||
min={5}
|
||||
max={3600}
|
||||
value={draft.interval}
|
||||
onChange={(v) => setDraftField('interval', v ?? 60)}
|
||||
addonAfter="s"
|
||||
style={{ flex: '1 1 0', minWidth: 0 }}
|
||||
/>
|
||||
<span style={{ flexShrink: 0 }}>
|
||||
{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={{ flex: '2 1 0', minWidth: 120 }}
|
||||
/>
|
||||
<Button
|
||||
disabled={agentNodes.length === 0}
|
||||
onClick={() =>
|
||||
setDraftField(
|
||||
'nodeIds',
|
||||
agentNodes.map((n) => n.id),
|
||||
)
|
||||
}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<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')}
|
||||
{t('pages.nodes.probeTasks.selectAllNodes')}
|
||||
</Button>
|
||||
)}
|
||||
<Space style={{ flexShrink: 0 }}>
|
||||
<Button type="primary" loading={saving} onClick={() => void onSave()}>
|
||||
{t('pages.nodes.probeTasks.save')}
|
||||
</Button>
|
||||
<Button onClick={() => setDraft(blankDraft())}>
|
||||
{t('pages.nodes.probeTasks.cancel')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -1040,17 +1040,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"intro": "由 agent 节点执行的 TCP 延迟探测。修改在各 agent 下一次拉取配置时生效(约 30 秒),无需重启。",
|
||||
"loading": "加载中…",
|
||||
"empty": "还没有探测任务,新增一个开始测延迟。",
|
||||
"add": "新增探测",
|
||||
"name": "名称",
|
||||
"target": "目标",
|
||||
"targetPlaceholder": "主机:端口,如 1.1.1.1:443",
|
||||
"interval": "间隔(秒)",
|
||||
"region": "地区",
|
||||
"ungrouped": "未分组",
|
||||
"autoJoin": "自动加入",
|
||||
"autoJoinOn": "自动",
|
||||
"nodes": "指派节点",
|
||||
"nodesPlaceholder": "选择节点",
|
||||
"selectAllNodes": "选择全部节点",
|
||||
"actions": "操作",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
|
||||
@@ -922,17 +922,16 @@
|
||||
"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",
|
||||
"selectAllNodes": "Select all nodes",
|
||||
"actions": "Actions",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
Reference in New Issue
Block a user