dsh-ensp 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +328 -0
- package/README.md +6 -2
- package/docs/UI-ANCHORS.md +10 -5
- package/package.json +2 -1
- package/src/cli/ensp.mjs +7 -0
- package/src/core/acceptance.mjs +20 -2
- package/src/core/build.mjs +14 -3
- package/src/core/compat.mjs +236 -227
- package/src/core/console.mjs +34 -16
- package/src/core/doctor.mjs +17 -14
- package/src/core/index.mjs +9 -2
- package/src/core/launcher.mjs +89 -9
- package/src/core/paths.mjs +190 -0
- package/src/core/provision.mjs +40 -4
- package/src/dsh/index.mjs +41 -1
package/README.en.md
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
English | <a href="README.md">中文</a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/dsh-ensp"><img alt="npm" src="https://img.shields.io/npm/v/dsh-ensp?style=flat-square&color=6c5ce7"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/dsh-ensp"><img alt="npm downloads" src="https://img.shields.io/npm/dm/dsh-ensp?style=flat-square&color=4b6fff"></a>
|
|
8
|
+
<a href="https://github.com/lizi600jin/dsh-ensp/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/lizi600jin/dsh-ensp?style=flat-square&color=eab308"></a>
|
|
9
|
+
<a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-GPL--3.0--or--later-blue?style=flat-square"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
# dsh-ensp
|
|
13
|
+
|
|
14
|
+
Automation for Huawei eNSP. Generates topologies, starts devices, pushes configuration in order, and checks the result of every command.
|
|
15
|
+
|
|
16
|
+
Three calling modes share one core:
|
|
17
|
+
|
|
18
|
+
- CLI: scripted batch operation
|
|
19
|
+
- MCP server: for Claude Desktop, Cursor and similar clients
|
|
20
|
+
- DeepSeek Harness plugin: model tools inside a session
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
### Generate topologies
|
|
25
|
+
|
|
26
|
+
Writes `.topo` files that eNSP opens and renders correctly. Works with the built-in scenarios or with devices and links you define yourself.
|
|
27
|
+
|
|
28
|
+
Model and interface layouts live in `src/core/models.mjs` and cover 16 models. When parsing a topology, port names come from the device's own `<interface>` block first; the same model can have a different port layout after an expansion card is added, and that declaration is the only reliable source.
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { twoPcLab, writeTopology } from 'dsh-ensp'
|
|
32
|
+
|
|
33
|
+
await writeTopology('lab.topo', twoPcLab({ vlan: 10 }))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { buildTopology, autoLayout } from 'dsh-ensp'
|
|
38
|
+
|
|
39
|
+
const topo = autoLayout(
|
|
40
|
+
buildTopology()
|
|
41
|
+
.add('R1', 'AR2220', { x: 180, y: 120 })
|
|
42
|
+
.add('R2', 'AR2220', { x: 460, y: 260 })
|
|
43
|
+
.link('R1', 'GigabitEthernet0/0/0', 'R2', 'GigabitEthernet0/0/0')
|
|
44
|
+
.finish()
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A few topology rules come from eNSP itself: interface groups must match the model, and link anchors must fall inside the icon bounding box. If any of them is wrong, eNSP opens a blank canvas and reports nothing. See [`docs/ENSP-FORMAT.md`](docs/ENSP-FORMAT.md).
|
|
49
|
+
|
|
50
|
+
### Start and stop devices
|
|
51
|
+
|
|
52
|
+
Locates the toolbar buttons automatically, so nothing has to be clicked by hand. After starting, it confirms each device one by one, then minimises the window so the desktop stays usable.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ensp probe # re-calibrate the button positions
|
|
56
|
+
ensp up lab.topo # open, start, confirm each device, minimise
|
|
57
|
+
ensp stop # shut every device down
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
eNSP allows only one instance. An abnormal exit can leave a zombie process with no threads, which keeps holding the single-instance lock and makes the next start open a blank window. The tool identifies and clears such processes before starting.
|
|
61
|
+
|
|
62
|
+
### Push configuration
|
|
63
|
+
|
|
64
|
+
Sends one command at a time and waits for the prompt after each. A prompt only counts once the echo of that command has arrived, so output still draining from the previous command cannot decide the current view. The first error aborts the batch and reports the line, the command and the device's raw output.
|
|
65
|
+
|
|
66
|
+
A line the device answers with "already exists" (`address already exists`, `VLAN already exists`, `route already exists` and similar) is recorded as skipped rather than failed. Editing one line and re-running is the common loop, and stopping on those lines means the script never reaches the line that actually changed. Every other error still aborts, because a genuine rejection usually means the following lines land on the wrong object.
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { pushConfig } from 'dsh-ensp'
|
|
70
|
+
|
|
71
|
+
const result = await pushConfig({
|
|
72
|
+
device: 'SW1',
|
|
73
|
+
port: 2000,
|
|
74
|
+
script: `
|
|
75
|
+
system-view
|
|
76
|
+
vlan 10
|
|
77
|
+
quit
|
|
78
|
+
interface GigabitEthernet0/0/1
|
|
79
|
+
port link-type access
|
|
80
|
+
port default vlan 10
|
|
81
|
+
quit
|
|
82
|
+
`,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
console.log(result.failedAt.line, result.failedAt.command, result.failedAt.error)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Wait for convergence
|
|
91
|
+
|
|
92
|
+
Routing protocols converge asynchronously, so a finished push does not mean neighbours are up. Such checks poll until the state is reached or the timeout expires.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import { waitForOspfFull, waitForOspfRoute, waitForOutput } from 'dsh-ensp'
|
|
96
|
+
|
|
97
|
+
await waitForOspfFull({ port: 2000, neighbourId: '2.2.2.2' })
|
|
98
|
+
await waitForOspfRoute({ port: 2000, prefix: '2.2.2.2/32' })
|
|
99
|
+
await waitForOutput({ port: 2000, command: 'display ip routing-table', until: /2\.2\.2\.2/ })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
All three return `{ ok, converged, output, attempts, waitedMs }`. A fixed sleep misjudges on a slow machine and wastes time on a fast one.
|
|
103
|
+
|
|
104
|
+
### Scenario presets
|
|
105
|
+
|
|
106
|
+
50 presets cover VLAN, STP, stacking, link aggregation, static routing, RIP, OSPF, BGP, IS-IS, MPLS, multicast, NAT, DHCP, ACL, QoS, port security, IPsec, IPv6, WLAN, firewalls and high availability.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
ensp preset # list by difficulty
|
|
110
|
+
ensp preset ospf-single # read one preset
|
|
111
|
+
ensp preset --search bgp # search by keyword
|
|
112
|
+
ensp preset campus-network --topo campus.topo # write that topology
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Each preset provides three things: the objects to create, the configuration order, and the commands that confirm the result. The actual commands are filled in per device.
|
|
116
|
+
|
|
117
|
+
Presets ship with a topology blueprint: devices, links and addressing are ready, and `--topo` writes a `.topo` that eNSP opens, with no drawing or address planning needed. Blueprints contain no configuration commands, so a preset remains a skeleton.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
ensp preset ospf-single --topo ospf-single.topo
|
|
121
|
+
ensp up ospf-single.topo
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The preset catalogue and the blueprints are both generated and checked. `docs/SCENARIOS.md` is rendered from `src/core/presets.mjs` and `src/core/labs.mjs`; `npm run docs:check` returns non-zero when the two disagree, and CI runs the same check.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run docs # regenerate docs/SCENARIOS.md
|
|
128
|
+
npm run docs:check # check whether it is stale
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Device image check
|
|
132
|
+
|
|
133
|
+
Some models have a catalogue entry without a shipped image, most notably `USG6000V` (its component has to be selected during eNSP installation). Such models can be placed on the canvas and saved to a `.topo`, but eNSP renders the topology and then quits, which shows up as consoles that never answer.
|
|
134
|
+
|
|
135
|
+
`ensp up` reads `res/items.xml` and the plugin image files before starting the client, and reports models that cannot start together with the missing piece:
|
|
136
|
+
|
|
137
|
+
```console
|
|
138
|
+
$ ensp up firewall-hrp.topo
|
|
139
|
+
error: topology uses devices this eNSP installation cannot start:
|
|
140
|
+
USG6000V (FW1, FW2): NGFW 镜像未安装(eNSP 安装时需勾选 USG6000V 组件)
|
|
141
|
+
|
|
142
|
+
This is not a bug in the topology: eNSP opens it, renders it, then quits. ...
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`ensp preset ... --topo` prints the same warning, and `ensp_build_topology` returns `unsupportedDevices`. The check only reads local files and does not start eNSP. Per-scenario device lists appear in [docs/SCENARIOS.md](docs/SCENARIOS.md) under the device image line.
|
|
146
|
+
|
|
147
|
+
### Window occlusion check
|
|
148
|
+
|
|
149
|
+
Starting devices requires clicking eNSP toolbar buttons, and a synthetic click goes to the window that owns that pixel. When another program covers eNSP, the mouse events go there and eNSP receives no input.
|
|
150
|
+
|
|
151
|
+
Before clicking, the tool checks the owner of the target point with `WindowFromPoint` and skips the click when the owner is not eNSP:
|
|
152
|
+
|
|
153
|
+
```console
|
|
154
|
+
$ ensp up lab.topo
|
|
155
|
+
error: eNSP is covered by another window at 1307,426: vmware (unknown).
|
|
156
|
+
The mouse events would go to that window, so nothing was clicked.
|
|
157
|
+
Bring eNSP to the front (restore it from the taskbar) and try again.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Calibration samples the window on a 5 by 4 grid and stops once any sample point belongs to another window. Calibration reads screen pixels, so an occluded window yields another program's pixels. After the click the owner is checked once more.
|
|
161
|
+
|
|
162
|
+
The tool cannot raise the eNSP window. Windows lets only the process holding foreground rights switch the foreground window, and a cross-process `SetForegroundWindow` is refused. Keep eNSP on top before running.
|
|
163
|
+
|
|
164
|
+
### Run permissions
|
|
165
|
+
|
|
166
|
+
This tool must run as administrator.
|
|
167
|
+
|
|
168
|
+
The eNSP client requires administrator rights and always runs at high integrity. Windows user interface privilege isolation (UIPI) drops input messages sent by lower-integrity processes, and synthetic clicks are among them. At normal privileges `SendInput` reports success but the input queue does not change, so eNSP receives nothing and clicks appear to do nothing. Started as administrator, input injection works.
|
|
169
|
+
|
|
170
|
+
Administrator rights solve input injection, not window stacking. eNSP still has to stay visible and on top.
|
|
171
|
+
|
|
172
|
+
Services, scheduled tasks and SSH sessions have no input-injection capability, and elevation does not change that. The `input-injection` check of `ensp doctor` reports whether the current session can be used.
|
|
173
|
+
|
|
174
|
+
### Doctor
|
|
175
|
+
|
|
176
|
+
When a start fails, locating the faulty item usually costs more than fixing it. `ensp doctor` splits the environment into seven checks, decides each one, and gives an actionable fix when one fails:
|
|
177
|
+
|
|
178
|
+
```console
|
|
179
|
+
$ ensp doctor --topology lab.topo
|
|
180
|
+
[ok ] ensp-installed: E:\\Huawei\\eNSP\\eNSP_Client.exe
|
|
181
|
+
[ok ] virtualbox: E:\\VirtualBox\\VBoxManage.exe
|
|
182
|
+
[ok ] console-ports: 2000-2100 free
|
|
183
|
+
[warn] memory: 949 MB free (each router needs ~256 MB)
|
|
184
|
+
fix: Close some applications; devices may boot very slowly or fail to start.
|
|
185
|
+
[ok ] device-images: AR2220 available
|
|
186
|
+
[FAIL] input-injection: SendInput accepted 2 event(s) but the session last-input tick did not change (session 1, console session 1)
|
|
187
|
+
fix: Run the tool elevated (as administrator) from an interactive desktop session.
|
|
188
|
+
[ok ] anchors: start (987,226) stop (1049,228)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The command does not start eNSP; it reads local files and runs one synthetic input probe, so it can run after any failure or before any operation. `--shallow` skips the slower input probe and `--json` prints the full structure. The matching tool is `ensp_doctor`.
|
|
192
|
+
|
|
193
|
+
The check order is fixed: client executable, VirtualBox, topology images, console ports, input injection, memory, anchors. Fix only the item that fails.
|
|
194
|
+
|
|
195
|
+
### Verification
|
|
196
|
+
|
|
197
|
+
A successful push does not mean the lab works. `ensp_verify` reads console ports from the `.topo`, runs the verification commands of that scenario, and judges by output content rather than by whether the command reported an error:
|
|
198
|
+
|
|
199
|
+
```console
|
|
200
|
+
$ ensp verify ospf-single --topology lab.topo
|
|
201
|
+
ospf-single · 单区域 OSPF
|
|
202
|
+
[curated checks]
|
|
203
|
+
[pass] neighbour-full on R1: display ospf peer brief
|
|
204
|
+
[pass] ospf-route on R1: display ip routing-table protocol ospf
|
|
205
|
+
|
|
206
|
+
accepted: 2/2 checks passed
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
About 30 scenarios have targeted assertions (OSPF needs `Full`, BGP needs `Established`, VRRP needs `Master`/`Backup`, MPLS VPN needs `Established` and a VPN routing table); the rest fall back to running the verification commands listed in the scenario. The decision logic lives in `EXPECTATIONS` in `src/core/acceptance.mjs`, and assertions support `contains`, `notContains`, `matches` and `minLines`. The matching tool is `ensp_verify`, and `ensp_scenario` reports whether a scenario has targeted assertions.
|
|
210
|
+
|
|
211
|
+
Device names in assertions map by role to the topology you actually have: blueprints use `R1`/`R2` while course assignments may use `RA`/`RB`/`RC`. Verification takes the first device with a console (`R2` takes the second), so a different name does not change the result.
|
|
212
|
+
|
|
213
|
+
Failed assertions are retried automatically. Routing protocols converge after the command returns, and right after `ensp apply` an OSPF neighbour is usually still in `ExStart` or `2-Way`, so a single sample would report a working lab as broken. The default wait is 60 seconds, `--wait` changes it, and a pass records how long it actually waited:
|
|
214
|
+
|
|
215
|
+
```console
|
|
216
|
+
$ ensp verify ospf-single --topology lab.topo
|
|
217
|
+
checking neighbour-full on R1
|
|
218
|
+
waiting 3s for neighbour-full on R1 (not contains "Full")
|
|
219
|
+
...
|
|
220
|
+
[pass] neighbour-full on R1: display ospf peer brief (converged after 27s)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Two kinds of failure skip the retry loop because retrying changes nothing: an unreachable console (device not started) and a command VRP rejects (wrong command). The former reports `console unreachable: ... ECONNREFUSED`, the latter reports `VRP rejected the command`; neither is disguised as an assertion mismatch.
|
|
224
|
+
|
|
225
|
+
The recommended order is `doctor`, `up`, `verify`: doctor decides the environment, up decides the devices, verify decides the configuration effect.
|
|
226
|
+
|
|
227
|
+
## Installation
|
|
228
|
+
|
|
229
|
+
### As a DSH plugin
|
|
230
|
+
|
|
231
|
+
Search `dsh-ensp` in the plugin market and install it, or:
|
|
232
|
+
|
|
233
|
+
```console
|
|
234
|
+
dsh plugin --profile desktop add dsh-ensp
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### As a CLI or library
|
|
238
|
+
|
|
239
|
+
```console
|
|
240
|
+
npm install -g dsh-ensp
|
|
241
|
+
ensp status
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### As an MCP server
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"mcpServers": {
|
|
249
|
+
"ensp": {
|
|
250
|
+
"command": "npx",
|
|
251
|
+
"args": ["-y", "dsh-ensp", "mcp"]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
14 tools are provided: `ensp_status`, `ensp_build_topology`, `ensp_open`, `ensp_start`, `ensp_stop`, `ensp_apply`, `ensp_apply_dir`, `ensp_query`, `ensp_probe`, `ensp_doctor`, `ensp_verify`, `ensp_scenarios`, `ensp_scenario` and `ensp_preset_config`.
|
|
258
|
+
|
|
259
|
+
## Requirements
|
|
260
|
+
|
|
261
|
+
| Item | Requirement |
|
|
262
|
+
|---|---|
|
|
263
|
+
| OS | Windows. eNSP has no other build |
|
|
264
|
+
| eNSP | Installed and working |
|
|
265
|
+
| Node.js | 18 or newer |
|
|
266
|
+
| Privileges | Run both this tool and eNSP as administrator, at the same integrity level |
|
|
267
|
+
|
|
268
|
+
The eNSP and VirtualBox install locations are detected automatically: `--exe` / `--vbox` first, then the `DSH_ENSP_EXE` / `DSH_VBOX` environment variables, then the `HKLM\SOFTWARE\Huawei\eNSP` registry key, then the usual install roots and `PATH`. Nothing needs configuring when either is installed on another drive, and a failed lookup lists every location it tried together with where that location came from.
|
|
269
|
+
|
|
270
|
+
At 125% or 150% display scaling the tool converts coordinates itself, so no resolution change is needed. If an eNSP upgrade moves the buttons, run `ensp probe` to re-calibrate.
|
|
271
|
+
|
|
272
|
+
## Quick start
|
|
273
|
+
|
|
274
|
+
Run as administrator (see "Run permissions" above):
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
ensp doctor # confirm this machine can run a lab
|
|
278
|
+
ensp preset ospf-single --topo lab.topo # write a topology
|
|
279
|
+
ensp up lab.topo # start the devices
|
|
280
|
+
ensp apply ./configs --topology lab.topo --save
|
|
281
|
+
ensp verify ospf-single --topology lab.topo # verify against the scenario
|
|
282
|
+
ensp stop
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Timing for one two-area OSPF lab (measured on Windows 11, i5, 150% scaling):
|
|
286
|
+
|
|
287
|
+
| Stage | Time |
|
|
288
|
+
|---|---|
|
|
289
|
+
| Write the `.topo` | 2s |
|
|
290
|
+
| Open and render the topology | 14s |
|
|
291
|
+
| Two AR2220 to a loginable prompt | 64s |
|
|
292
|
+
| Push 9 commands to each side | 8s |
|
|
293
|
+
| OSPF neighbours to Full | 40s |
|
|
294
|
+
|
|
295
|
+
## Thanks and licence
|
|
296
|
+
|
|
297
|
+
### Licence
|
|
298
|
+
|
|
299
|
+
[GPL-3.0-or-later](LICENSE).
|
|
300
|
+
|
|
301
|
+
Redistributing a modified version requires:
|
|
302
|
+
|
|
303
|
+
1. Keeping the copyright notice in `LICENSE`;
|
|
304
|
+
2. Naming the original project `dsh-ensp` and its repository <https://github.com/lizi600jin/dsh-ensp> in the source and the accompanying documentation;
|
|
305
|
+
3. Licensing the distributed work under GPL-3.0-or-later as well.
|
|
306
|
+
|
|
307
|
+
None of this applies when you use the project yourself without redistributing it.
|
|
308
|
+
|
|
309
|
+
MIT and Apache-2.0 were not chosen because both allow a modified version to be distributed closed source, which conflicts with the requirement that modifications also be published.
|
|
310
|
+
|
|
311
|
+
### Thanks
|
|
312
|
+
|
|
313
|
+
- Huawei eNSP: the simulator itself.
|
|
314
|
+
- The users who reported problems in issues.
|
|
315
|
+
|
|
316
|
+
## Disclaimer
|
|
317
|
+
|
|
318
|
+
This project is an automation shell. It contains neither eNSP nor any Huawei software or image. Install eNSP yourself and follow Huawei's licence terms.
|
|
319
|
+
|
|
320
|
+
Automating a graphical interface carries inherent risk, for example an occluded window, an eNSP upgrade or a change in display scaling. Validate in a non-critical environment first.
|
|
321
|
+
|
|
322
|
+
## Documentation
|
|
323
|
+
|
|
324
|
+
| Document | Content |
|
|
325
|
+
|---|---|
|
|
326
|
+
| [`docs/ENSP-FORMAT.md`](docs/ENSP-FORMAT.md) | `.topo` format rules |
|
|
327
|
+
| [`docs/UI-ANCHORS.md`](docs/UI-ANCHORS.md) | Interface coordinates and startup flow |
|
|
328
|
+
| [`docs/SCENARIOS.md`](docs/SCENARIOS.md) | Scenario presets |
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<strong>中文</strong>
|
|
2
|
+
<strong>中文</strong> | <a href="README.en.md">English</a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -61,7 +61,9 @@ eNSP 只允许运行一个实例。异常退出可能遗留不占线程的僵尸
|
|
|
61
61
|
|
|
62
62
|
### 下发配置
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
逐条发送,每条命令确认提示符返回后才继续。提示符必须在本条命令的回显之后出现,否则视为上一条命令的输出尚未结束,不会据此判断视图。任一命令出错即中断,并回报行号、命令与设备原始输出。
|
|
65
|
+
|
|
66
|
+
设备回答“已存在”的行(`address already exists`、`VLAN already exists`、`route already exists` 等)视为已达目标状态,记为跳过而不是失败。改一行再重跑是最常见的用法,如果这些行一出现就中断,脚本永远跑不到真正改动的那一行。其他错误仍然中断,因为真正的拒绝通常意味着后面的行也会落到错误的对象上。
|
|
65
67
|
|
|
66
68
|
```js
|
|
67
69
|
import { pushConfig } from 'dsh-ensp'
|
|
@@ -263,6 +265,8 @@ ensp status
|
|
|
263
265
|
| Node.js | 18 或更高版本 |
|
|
264
266
|
| 权限 | 以管理员身份运行本工具与 eNSP,两者需为同一权限级别 |
|
|
265
267
|
|
|
268
|
+
eNSP 与 VirtualBox 的安装位置自动探测:先看 `--exe` / `--vbox`,再看 `DSH_ENSP_EXE` / `DSH_VBOX` 环境变量,再看注册表 `HKLM\SOFTWARE\Huawei\eNSP`,最后看常见安装根目录与 `PATH`。装在非默认盘符时不必手工配置,探测失败会列出每一个尝试过的位置及其来源。
|
|
269
|
+
|
|
266
270
|
系统缩放设为 125% 或 150% 时,程序会自行完成坐标换算,不需要手动调分辨率。若 eNSP 升级后按钮位置改变,执行 `ensp probe` 重新标定。
|
|
267
271
|
|
|
268
272
|
## 快速开始
|
package/docs/UI-ANCHORS.md
CHANGED
|
@@ -48,9 +48,15 @@
|
|
|
48
48
|
4. 点击「开启设备」= window-relative (660, 59)
|
|
49
49
|
5. 判定启动成功:Get-NetTCPConnection 出现 2000..2100 的监听端口
|
|
50
50
|
(AR1220 约 60~120s;这不是“进程在不在”,是唯一可靠判据)
|
|
51
|
-
6. SW_MINIMIZE
|
|
51
|
+
6. SW_MINIMIZE 最小化,并把激活交还给最小化之前的那个窗口
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
最小化只改窗口状态,不转移激活。`ShowWindow(h, SW_MINIMIZE)` 之后窗口已经 iconic,
|
|
55
|
+
但 `GetForegroundWindow()` 仍然是 eNSP;此时从任务栏点它,第一击会被 Windows 当成
|
|
56
|
+
“修正激活一致性”吞掉,表现为点了没反应、要再点一次。因此最小化后必须显式
|
|
57
|
+
`SetForegroundWindow(previousForeground)`,并在 120ms 后回读确认。
|
|
58
|
+
停止设备同理:点击需要 eNSP 在前台,但用户要的是停止,操作结束后不该把 eNSP 留在前台。
|
|
59
|
+
|
|
54
60
|
### 关键纠正(都来自实测,推翻了早期假设)
|
|
55
61
|
|
|
56
62
|
| 早期假设 | 实测结论 |
|
|
@@ -65,10 +71,9 @@
|
|
|
65
71
|
|
|
66
72
|
## 遮挡是硬失败,不是可重试的小问题
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`vmware-vmx`(PID 9948),点击返回 `ok:true` 而 `VBoxManage list runningvms` 全程为空。
|
|
74
|
+
合成点击落在**拥有那个像素的窗口**上,不是落在预期窗口上。别的程序覆盖 eNSP 时,
|
|
75
|
+
鼠标事件由该程序接收,eNSP 收不到任何输入;而调用链每一步都会报告成功,调用方随后等待一批从未启动的设备。
|
|
76
|
+
因此遮挡按硬失败处理,不做重试。
|
|
72
77
|
|
|
73
78
|
所以插件在**点击前**先用 `WindowFromPoint` + `GetAncestor(GA_ROOT)` 查落点属主:
|
|
74
79
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ensp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Automate Huawei eNSP labs: generate .topo files, start and stop devices, and push configuration one command at a time with per-command error reporting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"cordis.patch.yml",
|
|
41
41
|
"screenshots.json",
|
|
42
42
|
"README.md",
|
|
43
|
+
"README.en.md",
|
|
43
44
|
"LICENSE"
|
|
44
45
|
],
|
|
45
46
|
"scripts": {
|
package/src/cli/ensp.mjs
CHANGED
|
@@ -76,6 +76,8 @@ ${C.bold('OPTIONS')}
|
|
|
76
76
|
--wait MS verify: how long a failing assertion may keep being retried (default 60000)
|
|
77
77
|
--topo PATH write the scenario's topology blueprint to a .topo file
|
|
78
78
|
--topology PATH the .topo file that is running (doctor, verify)
|
|
79
|
+
--exe PATH full path to eNSP_Client.exe (default: auto-detect via registry and install roots)
|
|
80
|
+
--vbox PATH full path to VBoxManage.exe (default: auto-detect via PATH and install roots)
|
|
79
81
|
--shallow doctor: skip the input-injection probe
|
|
80
82
|
--json machine-readable output
|
|
81
83
|
-h, --help show this help
|
|
@@ -86,6 +88,7 @@ ${C.bold('EXAMPLES')}
|
|
|
86
88
|
ensp verify ospf-single --topology lab.topo
|
|
87
89
|
ensp apply ./configs --topology lab.topo --save
|
|
88
90
|
ensp status --topology lab.topo
|
|
91
|
+
ensp up lab.topo --exe "C:/Program Files/Huawei/eNSP/eNSP_Client.exe"
|
|
89
92
|
ensp probe
|
|
90
93
|
ensp preset --search ospf
|
|
91
94
|
ensp preset ospf-single
|
|
@@ -518,6 +521,10 @@ async function cmdVerify({ positional, flags }) {
|
|
|
518
521
|
// ------------------------------------------------------------------------- main
|
|
519
522
|
async function main() {
|
|
520
523
|
const { verb, positional, flags } = parseArgs(process.argv.slice(2))
|
|
524
|
+
// --exe and --vbox are global: every verb that launches or inspects eNSP has
|
|
525
|
+
// to see the same install, and the launcher reads these variables anyway.
|
|
526
|
+
if (flags.exe) process.env.DSH_ENSP_EXE = String(flags.exe)
|
|
527
|
+
if (flags.vbox) process.env.DSH_VBOX = String(flags.vbox)
|
|
521
528
|
if (!verb || flags.help) {
|
|
522
529
|
console.log(HELP)
|
|
523
530
|
return flags.help && !verb ? 0 : 2
|
package/src/core/acceptance.mjs
CHANGED
|
@@ -34,8 +34,26 @@ import { readTopology } from './topology.mjs'
|
|
|
34
34
|
*/
|
|
35
35
|
export const EXPECTATIONS = {
|
|
36
36
|
'vlan-access': [
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// `minLines: 3` used to stand here, but a switch that has never been
|
|
38
|
+
// configured already lists VLAN 1, so it passed on a blank device. An
|
|
39
|
+
// untagged member alone is not enough either: every port starts as an
|
|
40
|
+
// untagged member of VLAN 1. What proves the lab is a member of a VLAN other
|
|
41
|
+
// than 1.
|
|
42
|
+
{
|
|
43
|
+
id: 'port-in-configured-vlan',
|
|
44
|
+
on: 'SW1',
|
|
45
|
+
command: 'display vlan',
|
|
46
|
+
expect: { matches: /^(?!\s*1\s)(\d+)\s+common[^\n]*UT:GE\d\/0\/\d+\(U\)/m },
|
|
47
|
+
},
|
|
48
|
+
// `contains: 'U'` matched the table header `U: Up;` and the `UT:` prefix, so
|
|
49
|
+
// it proved nothing either. Two access ports must carry the same non-default
|
|
50
|
+
// VLAN, which is what puts the two PCs in one broadcast domain.
|
|
51
|
+
{
|
|
52
|
+
id: 'access-ports-share-vlan',
|
|
53
|
+
on: 'SW1',
|
|
54
|
+
command: 'display port vlan active',
|
|
55
|
+
expect: { matches: /GE\d\/0\/\d+\s+access\s+(?!1\s)(\d+)[\s\S]{0,600}?GE\d\/0\/\d+\s+access\s+\1/ },
|
|
56
|
+
},
|
|
39
57
|
],
|
|
40
58
|
'trunk-interconnect': [
|
|
41
59
|
{ id: 'trunk-up', on: 'SW1', command: 'display interface brief | include up', expect: { minLines: 2 } },
|
package/src/core/build.mjs
CHANGED
|
@@ -197,14 +197,25 @@ export function twoPcLab({ vlan = 10, subnet = '192.168.10' } = {}) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/**
|
|
200
|
-
* Switch configuration for {@link twoPcLab}: both ports into one VLAN
|
|
201
|
-
*
|
|
200
|
+
* Switch configuration for {@link twoPcLab}: both ports into one VLAN, plus the
|
|
201
|
+
* matching Vlanif.
|
|
202
|
+
*
|
|
203
|
+
* The Vlanif is not decoration. `twoPcLab` gives both PCs a gateway, so a lab
|
|
204
|
+
* without a Vlanif is a lab whose PCs cannot reach their own gateway, and any
|
|
205
|
+
* follow-up command that assumes the interface exists (`interface Vlanif10`)
|
|
206
|
+
* fails with "The VLAN does not exist". Creating the VLAN alone does not create
|
|
207
|
+
* its interface; VRP only does that when asked.
|
|
208
|
+
*
|
|
209
|
+
* @param {{vlan?: number, subnet?: string}} [options]
|
|
202
210
|
*/
|
|
203
|
-
export function twoPcLabConfig({ vlan = 10 } = {}) {
|
|
211
|
+
export function twoPcLabConfig({ vlan = 10, subnet = '192.168.10' } = {}) {
|
|
204
212
|
return [
|
|
205
213
|
'system-view',
|
|
206
214
|
`vlan ${vlan}`,
|
|
207
215
|
' quit',
|
|
216
|
+
`interface Vlanif${vlan}`,
|
|
217
|
+
` ip address ${subnet}.254 255.255.255.0`,
|
|
218
|
+
' quit',
|
|
208
219
|
'interface GigabitEthernet0/0/1',
|
|
209
220
|
' port link-type access',
|
|
210
221
|
` port default vlan ${vlan}`,
|