Add Multiple Servers: bulk paste-a-list onboarding with shared credentials (both apps) - #1549
Merged
Merged
Conversation
…tials (both apps) Adds an "Add Multiple Servers" entry beside Add Server in Lite and the Darling viewer: paste a server list (server[, display][, database]), set one shared auth + encryption block, optionally Test All, and add every non-duplicate row at once. Motivated by the onboarding wall of adding a 40-server estate one dialog at a time, re-typing the same login into each. - Shared parser (PerformanceMonitor.Common/BulkServerListParser.cs): the normative comma/tab grammar with #-comments, trailing-field tolerance, and per-line errors, mirrored-tested in both suites so the apps can't drift. - One secret, never N: Lite mints a single auto-named CredentialProfile every row references (created only when >=1 new row survives dedupe); Darling resolves the shared credential once to a single DPAPI blob (Protect called exactly once) stamped on every row. No plaintext anywhere. - Duplicates skipped via the ratified OrdinalIgnoreCase gate over the shared ServerIdHelper storage name, keyed from the BUILT row (one composition feeds the gate and the stored id); blank database maps to NULL, never "master". - Test All is informational only: Lite runs bounded-8 parallel, never-faulting, cancellable probes; Darling runs an honest sequential test_connect sweep (the service drains its command queue serially) with stop-between-rows cancel and no orphaned command rows. - Entra MFA is excluded from bulk (an interactive popup per server is a popup storm); a mapping-helper belt in each app rejects any resolved un-honorable auth at the single choke point feeding both Test All and Add. The single Add-Server dialog is unchanged (still the MFA path); the deprecated Full Dashboard is untouched. Full Lite.Tests (1352) and Darling.Tests (2148 + 134 skipped) pass, including both ThemeCompletenessTests and all capability pins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase-6 review finding (cosmetic): the Test-All run freezes the paste box so a TextChanged re-parse can't rebuild the row VMs under in-flight status writes, but the async Add path left it editable -- a mid-add edit would orphan MarkRow's failure statuses onto discarded VMs (adds themselves are unaffected; they work from the parse snapshot). Freeze it during Add too, symmetric with Test All. Lite needs nothing: its add is fully synchronous, so the reentrancy window does not exist there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull Bot
pushed a commit
to ehtick/PerformanceMonitor
that referenced
this pull request
Jul 22, 2026
…erver) Add the server-onboarding MCP write tools so an MCP client can stand up or tear down FLEET monitoring conversationally - the service-side twin of the Viewer's Add / Manage Servers dialogs, and the sibling of the erikdarlingdata#1600 Custom Views and erikdarlingdata#1608 alert-tuning MCP write tools. - add_servers (BULK): a JSON array of server objects, processed sequentially - validate -> case-folded dedupe (shared ServerIdHelper.BuildStorageName gate, erikdarlingdata#1549) -> in-process DarlingServerConnector.ProbeAsync (the service holds the network path + credentials, so no test_connect command plane) -> DarlingSecrets DPAPI-encrypt (round-trips at collection) -> INSERT mirroring SeedMonitoredServersAsync. Per-server added/duplicate/connection_failed/invalid; a connection failure does not abort the batch; Entra/MFA rejected; encrypt_mode + trust_server_certificate exposed. Returns {added, skipped, failed, results}. - remove_server: resolve via the same resolver the read tools use, DELETE the config_monitored_servers row. - Grant: mcp role gets INSERT/UPDATE/DELETE on config.config_monitored_servers (provisioning, not migration); the encrypted_password column stays SELECT-carved (write a credential, never read one back). The erikdarlingdata#1608 config_service beacon column-grant already covers the monitored-servers bump trigger. - MCP instructions (85 -> 87 tools), cross-app ratchet (Darling-only), /api/read write-exclusion, and the README MCP blast-radius (credential-on-the-wire note). Verified: Darling service + Darling.Tests build clean; Darling.Tests 2847 passed / 0 failed / 146 gated-live skipped (DARLING_TEST_PG cleared); Lite.Tests CrossAppMcpToolInventoryPinTests 2 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Adding a monitoring estate one server at a time is the onboarding wall: a 40-box fleet meant opening the single Add-Server dialog forty times and re-typing the same SQL login into every one. Both apps needed a way to paste a list and add it all at once — with the shared credential entered a single time, not forty.
Design
An "Add Multiple Servers" button beside Add Server in both Lite and the Darling viewer opens a paste-a-list dialog.
PerformanceMonitor.Common.BulkServerListParserparsesserver[, display name][, database]per line: comma- or tab-separated (tab wins when present, so a spreadsheet paste keeps commas inside a field), trimmed, trailing empty fields dropped (Excel's trailing cells),#comments and blank lines skipped with line numbers still correct, and empty-server / too-many-fields reported as per-line errors rather than silently dropped. Pure and synchronous; the parser does no dedupe or normalization (both are app-side by design). A mirrored test file in both suites pins the grammar so the two apps can't drift.CredentialProfile(unique-suffixed on a name clash) that every added row references, created only when at least one new row survives dedupe (a fully-duplicate paste leaves no orphan profile). Darling has no store-side profile table, so it resolves the shared credential once to a single DPAPI-LocalMachine blob —ViewerServerSecret.Protectcalled exactly once — and stamps that same blob onto every server row and everytest_connectprobe. No plaintext credential is ever written.HashSet<string>(OrdinalIgnoreCase)over the sharedServerIdHelper.BuildStorageName(name, db, readOnlyIntent)identity, seeded from existing servers' real keys and derived from the built row (one composition feeds the gate and the storedserver_id). Case-folding is the point: an existingSQL01skips a pastedsql01even though their raw hashes differ, while a per-database Azure entry and a read-only-intent connection stay distinct. A blank database maps to NULL, nevermaster— coercing it would mint a mismatched identity and split a server's collected data from its single-added twin.OperationCanceledExceptionon Cancel would crash the app — the never-faulting probe structure forbids it) and honoring the configured connection timeout. Darling routes probes through the service'stest_connectcommand, and because the service drains its queue serially, Test All is a sequential sweep with live per-row status and a Cancel that stops between rows — the in-flight probe finishes and self-deletes its credential-bearing command row, so no orphaned rows and no parallelism theater.Add writes every valid non-duplicate row (per-row failures reported, batch continues), returns an added / skipped / failed summary, and does not auto-connect (the background collector picks new rows up on its next cycle).
Parity
Both apps get the same feature from the same shared parser and the same dedupe identity. The two documented asymmetries are intentional and match each app's architecture: Lite mints one shared credential profile while Darling stamps one shared DPAPI blob (the store has no profile concept), and Lite's Test All is genuinely parallel while Darling's is sequential (the service drains commands serially, so viewer-side parallelism would be theater). The single Add-Server dialog is not edited in either app (it remains the MFA path and keeps the per-server niceties bulk omits), and the deprecated Full Dashboard is untouched.
Test results
dotnet build(Debug): Lite 0 errors, Darling viewer 0 errors.Lite.Tests: 1352 passed, 0 failed (incl. the mirrored parser cases, theBuildServerConnectionmapping + dedupe-gate pins, andThemeCompletenessTests2/2).Darling.Tests: 2148 passed, 134 skipped (live-Postgres-gated), 0 failed (incl. the mirrored parser cases, theBuildMonitoredServerRow/BuildTestConnectServermapping + dedupe-gate pins, andThemeCompletenessTests2/2).DataGridFilterManagerscan and the plan-viewer ratchet stay green).git diff --ignore-cr-at-eol --name-only(6) equalsgit diff --name-only(6); the 8-file gap togit status --short(14) is exactly the new untracked files.🤖 Generated with Claude Code