Commit graph

108 commits

Author SHA1 Message Date
473dccafff Version 1.3.0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
12cf5a1e94 Review 1.3: a request that fails in an unexpected way is completed as failed, not left Running
HandleAsync only turned a few exception types into the request's error; anything
else (a lock or command timeout, a JSON or argument error) escaped, leaving the
row Running and the page waiting until housekeeping failed it minutes later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
a6a72661e7 Review 1.3: a routine discovery that could not be stored is not retried every 2 seconds
When saving a discovery failed in the repository (deadlock victim, lock timeout,
connection), LastDiscoveryAt stayed unset and the next round 2 seconds later
listed the same instance's databases again, loading the customer's server. The
agent now waits at least 15 minutes between routine discoveries of an instance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f0c5d01294 Review 1.3: the overview works before the agent has migrated to 0016
GetInstancesAsync now reads insight.ConfiguredInstances only when it exists, so a
1.3 web UI (e.g. upgraded first on its own server) still shows the overview
against a repository a 1.2 agent has not migrated yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
a66e3f2b3b Review 1.3: upgrade note on AX findings to the hub (now opt-in per database)
1.2 sent Dynamics AX findings to the hub with the rest; 1.3 sends them only for
databases where AX hub sharing is ticked, default off, so sharing stops at the
upgrade until it is turned on. docs/opdatering.md gets an upgrade section that
says so first, with the other 1.3 changes, and the release text points to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f5ff42ca83 Review 1.3: an unreadable agent key file is kept, not overwritten
LoadOrCreate replaced a key file it could not decrypt, which made the old key
unrecoverable even when only the machine (DPAPI) had changed and the file could
have been put back. The file is now moved aside as credential-key.bin.unreadable-*
and the RSA object of the failed import is disposed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
356e102126 Review 1.3: redact logins and user names in the configuration API
API v1 returns logins and people's names as [udeladt] unless
ApiIncludesSensitiveEvidence is on, but /monitored-instances and /agent-requests
returned the SQL login, createdBy/updatedBy/requestedBy/selectionChangedBy, the
login the agent arrived as and 'Login failed for user ...' texts, and the GRANT
script filled in with the login. They are now redacted the same way (the GRANT
script falls back to the shipped template). ChooseAsync also answers 404 for an
unknown instance instead of 422.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
cade91aa4a Review 1.3: a request still waiting for the agent is reused, not queued again
Pressing Test forbindelsen repeatedly, or a script calling the API in a loop,
added a row per call to insight.AgentRequests, each run by the agent in turn. The
same pending request (kind, instance, parameters) is now returned instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
2adeef4597 Review 1.3: local Administrators may change the configuration until AdminGroups is set; MSI property ADMIN_GROUPS
With SQLInsight:Web:AdminGroups empty nobody could add the first server. The
installer now takes ADMIN_GROUPS (';'-separated) and writes it to the web's
sqlinsight.ini; while the setting is empty, members of the web server's
BUILTIN\Administrators (S-1-5-32-544 among the user's group SIDs; a remote
Negotiate login has the full token, a UAC-filtered deny-only SID does not count)
may change it. The page says who may.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
8e1b877fda Review 1.3: overview leaves out removed instances and marks paused ones
The overview kept listing removed and paused instances with their last data and
counted them in the totals and alerts. Removed instances now disappear from the
overview, Alle fund, /api/v1/instances and /metrics (their history stays for
reports and the instance page); paused ones are shown as "Sat på pause" and
are left out of the totals, the urgent findings and the alerts (Prometheus gets
sqlinsight_instance_paused instead of instance_up = 0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
4c65248727 Review 1.3: an instance removed from the configuration file is marked removed, not deleted
The agent deleted ConfigFile rows no longer in the file, and with them (cascade)
the pause and the database choices: commenting an instance out for a restart lost
them. It is now soft-deleted like a Web instance and brought back, choices and
all, when it returns to the file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
01c04ac95a Review 1.3: database choice and discovery take turns (sp_getapplock)
The web's database choice and the agent's discovery both update many rows of an
instance's insight.ConfiguredDatabases in one transaction, in different orders,
which can deadlock. Both now take an application lock per instance first, as
the finding saves do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
29e1909e98 Review 1.3: one implementation of a service account's SQL login
RepositorySetup.LoginFor/IsLocal duplicated Core's ServiceAccounts, which the web
uses for the GRANT script. Setup now uses ServiceAccounts too, so the login the
installer grants and the one the web shows cannot drift apart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
5ad9355130 Docs: Servere og databaser (1.3)
New docs/servere-og-databaser.md (on the community site too): adding servers,
who may change it (AdminGroups), connection test and GRANT script run by the
agent, database choice, configuration file servers, how SQL login passwords are
sealed and why, the repository grants, and the API. Installation, integration
(scope config:write) and data processing (AX findings to the hub are off by
default) point to it; AdminGroups in the web's appsettings.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
f3ef0cbec7 Web: Servere og databaser, and the same in API v1 (scope config:write)
A page under Overvågning lists the monitored servers (from SQLInsight Web and,
read-only, from the agent's configuration file), adds a server (name\instance,
optional port, Windows via the agent's account or a SQL login, encryption),
pauses, resumes and removes it, and finds instances via the agent (SQL Server
Browser). Each server's page shows the agent's connection test with the rights
it has and lacks, the least-privilege GRANT script for the login the agent
arrives as, and the discovered databases (state, size, recovery model,
compatibility level, Dynamics AX) to tick for monitoring, with "overvåg
automatisk nye databaser" and AX findings to the hub per database. Pages
refresh while the agent works on a request.

Only SQLInsight:Web:AdminGroups (new) may change anything; others see it.
SQL login passwords are sealed with the agent's public key on arrival and
never shown or returned; a new server or login needs the password again.

API v1: /monitored-instances (list, add, get, patch pause/monitor-new,
connection, delete), …/connection-test (start, latest with missing
permissions), …/discovery, …/databases (read, choose), …/grant-script,
/instance-discovery and /agent-requests/{id}. Changes need the new token scope
config:write, or AdminGroups for Windows users.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
81b8376026 Agent: instances from the repository, request queue, sealed passwords, AX hub opt-in
The agent mirrors SQLInsight:Instances into the repository at start (read-only
"fra konfigurationsfil" rows, every database monitored as before) and reads the
configuration on every tick: instances added, paused, removed or changed in
SQLInsight Web, and database choices, take effect without a restart. If the
repository cannot be read before the first load, the configuration file alone
is used.

AgentRequestService runs connection tests (with the permission check), database
discovery and instance discovery (SQL Server Browser, registry) as the agent's
own identity, and refreshes every instance's database list hourly. The agent
keeps an RSA key (DPAPI LocalMachine, restricted folder) and publishes its public
key and identity; SQL login passwords sealed by the web UI are opened only here.

Dynamics AX findings and AX rule outcomes are sent to SQLInsight Hub only for
databases where it is turned on (default off).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
b2fa78411f Collectors: monitor only chosen databases; database discovery and permission check
CollectorContext carries the instance's DatabaseSelection; the database,
index, Query Store and community-rule collectors skip databases that are not
chosen (system databases always run). DatabaseDiscovery lists every database
with state, size, recovery model, compatibility level and access, and
recognises Dynamics AX with the same signature as the AX rules, through
three-part names only (2000 and 2005+ variants). PermissionCheck reports which
rights from grant-least-privilege.sql the login has (HAS_PERMS_BY_NAME,
version-aware; not possible on 2000).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
1199b1aa86 Repository: configured instances, discovered databases, agent request queue (0016)
insight.ConfiguredInstances (Web or ConfigFile source, soft remove, sealed
password), insight.ConfiguredDatabases (discovery result, monitored, AX to hub),
insight.AgentRequests (queue for connection tests and discovery run by the agent)
and insight.AgentKeys (the agent's public key and identity). The web login gets
INSERT on ConfiguredInstances and AgentRequests and UPDATE on named columns only;
no DELETE, no Source/Name change, nothing the agent records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
8776faefb3 Instance configuration core: database selection, sealed credentials, agent request contracts
DatabaseSelection decides which databases the database-specific checks run in
(system databases always), and which may send Dynamics AX findings to the hub
(default none). CredentialEnvelope seals a SQL login password with the agent's
RSA public key (RSA-OAEP-SHA256 + AES-256-GCM), bound to server and login, so only
the agent can open it. Shared contracts for connection tests and discovery, server
address validation, the service account login rule, and the least-privilege GRANT
script filled in for one login.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
2284e7f082 SQL Scripts: one reviewable script per rule
A DBA should be able to review every statement SQLInsight sends to a
monitored server before granting it access. "SQL Scripts/<rule-id>.sql"
holds, per rule (82 built-in, 16 community), a Danish header (what and
why, how C# judges the data, thresholds with config keys, severity,
exact GRANTs, versions, collector file and member) and the collector's
SQL verbatim, every version variant as its own batch.
"_collectors/<Collector>.sql" holds each collector's queries once.

The only statement that is not strictly read-only is documented in the
scripts: the deadlock collector's session temp table (#system_health).

Inline SQL in the collectors moved to internal constants and Build*Sql
methods (no text changed), so SqlScriptTests can check that the scripts
still contain the SQL the code sends, that every rule has a script, that
every collector query is registered, and that nothing writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:20:06 +02:00
28bd148691 Version 1.2.0
Some checks failed
deploy community / deploy (push) Successful in 16s
deploy hub / deploy (push) Successful in 23s
CI / Build og unit tests (push) Successful in 6s
CI / MSI-installer (push) Successful in 15s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:47:20 +02:00
d9e3b4ee6d Review 1.2: read only the default trace's current file between rollovers
With ALTER TRACE the error log collector read every default trace file
(up to 5 x 20 MB) every 5 minutes. While the current file is the one read
last time, everything since then is in it, so only that file is read;
after a rollover (and the first time) all files are read once as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:38:34 +02:00
8e551bcaa6 Review 1.2: document the new collection limits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:54 +02:00
3f2e959f18 Review 1.2: list the new API v1 endpoints in the integration docs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:41 +02:00
d070cd872b Review 1.2: scan the version store for its generators at most hourly
sys.dm_tran_top_version_generators reads the whole version store. It ran
every 5 minutes while the version store was over half the threshold,
i.e. exactly when it is large and expensive to scan, and a timeout failed
the whole tempdb collection. It now runs at most once per hour (the
previous answer is kept in between, with its time) and a failed scan
keeps the previous answer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:35:00 +02:00
4ceb64f2d5 Review 1.2: index usage after a counter reset without a restart
sys.dm_db_index_usage_stats also starts over when a database is taken
offline, closed by AUTO_CLOSE or (2012/2014) an index is rebuilt. The
delta was then clamped to 0, so the usage since the reset was lost from
the daily history and the tracked totals. A lower counter now counts as
new usage. IndexInfo.Key also used the literal text "001f" as its
separator instead of the unit separator character.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:34:17 +02:00
a01ec374a4 Review 1.2: keep SQL Server's error message out of the replication reason
When the distribution database could not be read, SQL Server's message
(which names the agent's login) was appended to Unavailable. That text is
the rules' "not measurable" reason and is returned unredacted by
/rule-states and /replication. It is now UnavailableDetail: shown on the
local instance page and [udeladt] in the API unless
ApiIncludesSensitiveEvidence. The agent list is also capped (TOP 500).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:33:25 +02:00
fcf2466738 Review 1.2: bound the activity collector and avoid duplicate rows
The open transactions and long requests had no TOP, so a busy server could
return (and store in the snapshot every minute) thousands of rows with
statement texts. Now at most 100 transactions (large log first, then the
oldest) and the 50 longest requests.

The transactions were joined to sys.dm_exec_requests, which has one row
per request: a MARS session with an open transaction gave duplicate rows,
and the MERGE into insight.LongTransactions then failed on the primary
key every minute. The first request is now taken with OUTER APPLY TOP 1,
and the save keeps one row per transaction.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:32:40 +02:00
a256e4c8cc Review 1.2: say correctly what each error log source shows
The error log page called the default trace "the whole error log" and
said it has every line, and the ring buffer source told SQLI-LOG-002/005
to grant ALTER TRACE, which does not make 833 or stack dumps visible
(they are only in the error log itself). The rules now say that it takes
securityadmin, why SQLInsight does not grant it, and the exact statement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:31:53 +02:00
70934e7562 Review 1.2: error log entries are no longer counted again
The default trace was filtered on whole seconds only, so the newest entry
read (ReadUntil) came back at every collection until a newer one was
written: one 824 became a new event every 5 minutes. Entries at or before
ReadUntil are now left out, as for xp_readerrorlog.

The ring buffer's times were computed to whole seconds from the clock at
each read and moved by up to a second between reads, so the newest entry
could be counted twice. They are now computed to the millisecond with a
small tolerance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:31:00 +02:00
1bbe5eb8eb Plan analysis test: start from an empty plan cache
With the new collectors the full suite fills the plan cache with heavier
statements, so the light probe statements could fall outside the 50
heaviest. The test clears the cache first (test only; SQLInsight never does).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:14:54 +02:00
d27323a1a4 Availability groups: cluster node or witness down (D)
The availability group collector also reads sys.dm_hadr_cluster_members
(empty or unavailable without a WSFC), and SQLI-AG-003 (High) reports a
node or witness that is not up, with how many members are still up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:56:43 +02:00
6cc65e3c75 Index usage over time and never-updated statistics (8g, F)
The index collector now sums index reads and writes between collections
across restarts of SQL Server (and of the agent, by seeding from the
stored snapshot) and keeps them per day in insight.IndexUsageDaily
(migration 0015, HourlyDays). SQLI-IDX-002 judges an index once it has
been followed for 7 days instead of needing 7 days of uptime.
GET /api/v1/instances/{name}/index-usage lists usage per index.

SQLI-STAT-001 also reports statistics that were never updated on tables
with rows; statistic rows now come from the table's heap or clustered
index, as a never-updated statistic has no row count of its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:55:22 +02:00
473a7d0be3 Replication from the distributor (8f)
On a distributor, a 15-minute collector reads each distribution agent's
latest run (status, latency, message) and replication errors the last
day from the distribution database. That needs SELECT on
MSdistribution_agents, MSdistribution_history and MSrepl_errors, which
the agent does not have by default (never the replicated commands);
without it SQLI-REPL-001 (agent failed/retrying, High) and SQLI-REPL-002
(latency > 5 min, Medium) are "not measurable" with the exact grant, and
"not relevant" elsewhere. Shown on the instance page and in
GET /api/v1/instances/{name}/replication. Tested against a real
distributor set up in the SQL 2022 container.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:48:46 +02:00
66129d99fc Stopped automatic SQL Server services (8e)
SQLI-SVC-001 (High) reports a service set to start automatically that is
not running (SQL Server Agent, Full-text launcher, Launchpad …), from the
sys.dm_server_services data the configuration collector already reads
(2008 R2 SP1+; not relevant before). The service account is sensitive.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:43:26 +02:00
4d18d20ab0 tempdb and version store (8c)
A five-minute collector reads tempdb space (free, version store, user and
internal objects, whether files can grow), the 'Version Store Size (KB)'
counter, the sessions using most tempdb, the oldest snapshot transaction
and, only when the version store is large, the databases generating
versions. Trend and heavy sessions survive restarts; used space and the
version store become metrics with explanation and assessment.

Rules SQLI-TEMP-001 (version store > 1 GB or steadily growing),
SQLI-TEMP-002 (nearly full and cannot grow) and SQLI-TEMP-003 (session
above 1 GB for 15 minutes), configurable under SQLInsight:Tempdb.
"tempdb lige nu" on the instance page; GET /api/v1/instances/{name}/tempdb.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:42:38 +02:00
6f7839d011 Long and large transactions and long-running statements (8b, 8d)
A one-minute collector reads open user transactions (age, log bytes used
and reserved, database, session, login, program, statement) and user
requests running longer than the threshold, leaving out the agent's own
and system sessions, BACKUP/RESTORE/DBCC (long operations) and requests
waiting on purpose. Transactions open a minute or holding 100 MB are kept
in insight.LongTransactions (migration 0014, RawDays).

Rules SQLI-TRAN-001 (open > 10 min, High at 6x), SQLI-TRAN-002 (> 1 GB
log) and SQLI-REQ-001 (statement > 15 min), configurable under
SQLInsight:Activity. Shown "right now" on the instance page and in the
time window analysis and PDF; GET /api/v1/instances/{name}/activity and
the analysis JSON leave logins and statements out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:36:41 +02:00
e6840e40d7 SQL Server error log without elevated rights (8a)
A five-minute collector reads important error log entries from the best
source the agent's rights allow: the error log itself (xp_readerrorlog,
only with securityadmin), the default trace's ErrorLog and Audit Login
Failed events (optional ALTER TRACE), or the exception and connectivity
ring buffers (VIEW SERVER STATE: numbers and severity, failed logins).
Repetitions are summed per error; the watermark is the newest entry
read, because the default trace is buffered. Stored in
insight.ErrorLogEvents (migration 0013, EventDays), resumed after restart.

Rules SQLI-LOG-001..006: corruption 823/824/825, I/O over 15 s (833),
severe errors (17+), login failure bursts (configurable), stack dumps and
AG errors; rules needing the log text are "not measurable" with the
right to grant. Page /instanser/{navn}/fejllog (menu Overvågning) and
GET /api/v1/instances/{name}/error-log with messages redacted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:30:41 +02:00
cb2f8fcbce Version 1.1.0: disk, configuration, privilege audit, wait types, explanations, API v1, foldable menu
Some checks failed
deploy community / deploy (push) Successful in 15s
deploy hub / deploy (push) Successful in 23s
CI / Build og unit tests (push) Successful in 7s
CI / MSI-installer (push) Successful in 14s
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:00:33 +02:00
55fd389d10 Dynamics AX 3.0 reference schema (from a customer's AX 3.0 database)
Table and column names only, no data. Used as the facit for AX 3.0 rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 10:00:33 +02:00
0dc7866808 Separate "check by hand" from "not relevant" for version-gated rules
A rule whose topic applies but whose way of measuring needs a newer
version or patch now gives the new outcome ManualCheck with concrete
Danish guidance, instead of NotApplicable. It is marked explicitly per
rule (IRule.ManualCheck) and only used when every missing capability is
a measurement capability (InstanceCapabilities.IsMeasurement): CHECKDB
status (SQLI-DB-009), VLFs (SQLI-DB-011), spills and memory grants
(SQLI-PLAN-004/005) and deadlocks on 2005 (SQLI-DLK-001). Query Store,
AG and Azure-managed topics stay "not relevant".

Shown in a visible "Skal tjekkes manuelt" section on the instance page,
a KPI on the overview, a "Tjek manuelt" column in the management
report's controls, manualChecks in /api/v1/instances and manualCheck in
rule-states; not counted as not measurable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:59:50 +02:00
f3551b1bf0 Support cases through the API: create, read and close
- POST/GET /api/v1/support-cases[/{id}] and POST .../{id}/close in
  SQLInsight Web, sharing the /support form's logic: same checks, finding
  prefill without sensitive evidence, Idempotency-Key as the request id,
  202 Accepted with Location, problem+json 403 without the support:write
  scope or a support license. RequestedBy is the Windows user or
  token:<name>.
- Closing: "Luk sag" on /support and the API record CloseRequested
  (migration 0012, column-level UPDATE for the read-only web login); the
  agent sends it to the hub with backoff and sets Closed when confirmed;
  a pending case is closed once it has a number; an older hub list never
  turns CloseRequested back to Open.
- Hub: POST /api/v1/cases/{reference}/close for the license's own cases
  (others 404), idempotent, ClosedBy/ClosedComment (EF migration
  CaseClosedBy). Portal closing records "SEOSoft: <user>"; the customer
  page shows who closed and why; the support report and CSV get "Lukket af".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:47:58 +02:00
30d89acbd7 API v1: findings, rules, reports and page data as JSON, with OpenAPI
- /api/v1/findings gains status (active/resolved/all), from/to, database,
  ruleId, category, sort and paging (limit + X-Continuation header; the
  response stays an array). /findings/{id} with fix script and history.
- /rules (built-in, community, local) and /instances/{name}/rule-states.
- /reports and /reports/{driftsrapport|analyse|rettigheder} as JSON or PDF,
  built from the same models as the pages and PDFs.
- Page data: analysis, top-sql, disks, configuration, privileges (one or
  all instances) and wait types. Statement texts, logins, names and
  service accounts are "[udeladt]" unless ApiIncludesSensitiveEvidence.
- OpenAPI at /api/v1/openapi.json; a test fails when an endpoint is
  missing from it.
- API tokens get scopes ("read", "support:write"); the old form is read
  only, and new-api-token takes --scope. Token names show as token:<name>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:27:10 +02:00
cc71fb12c6 Menu in foldable groups
The web menu is grouped with details/summary (no script, CSP safe):
Overvågning, Analyse, Konfiguration og sikkerhed, Rapporter, Dynamics AX
(hidden until a database is recognised as AX), Support, System and
Community. The group holding the current page is open, including pages
under /instanser/{navn}/. Per-instance entries go through /vaelg/{side}
(straight to the instance when there is only one). On narrow screens
the whole menu folds behind a "Menu" checkbox toggle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:17:43 +02:00
384e1f154f Long operations, full-text rules and statement activity
- Long operations: a one-minute collector reads operations with progress
  from sys.dm_exec_requests (BACKUP, RESTORE, DBCC, REORGANIZE, SHRINK,
  ROLLBACK): command, database, start, percent, expected end, login.
  Stored in insight.LongOperations (migration 0011, RawDays), shown on
  the instance page, in the time window analysis and its PDF. The last
  10 durations per command and database survive restarts; SQLI-OPS-001
  flags a backup/restore running over twice the median.
- Full-text community rules COM-FT-001..005 (metadata only, 2005 syntax):
  disk-full or paused/throttled catalogs, stale manual/off change
  tracking indexes and disabled indexes.
- Statement activity (CPU time / time in plan cache): column in top SQL
  and SQLI-CPU-002 when above SQLInsight:TopSql:ActivityThresholdPercent
  (default 50) for at least an hour. Never clears the plan cache.
- BuiltInCollectors.Create takes a CollectorSettings record.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:07:29 +02:00
7a908cc28b Explain and assess measurements for the customer
MetricAssessment rates CPU (SQL/other), batch requests, compilations
(against batches), page life expectancy (300 s per 4 GB buffer pool,
average and dips over 50 %, max server memory unset), user connections,
memory grants pending, processes blocked and disk latency as OK, Bemærk
or Reagér with a concrete next step and link, plus a plain Danish
"Hvad betyder det?" for each. Shown under every graph on the instance
page (now all eight metrics) and in the analysis, per drive in the disk
table, and in the analysis PDF.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:57:10 +02:00
20671bfb06 Wait type drill-down with active session sampling
A new light collector samples sys.dm_exec_requests every 15 seconds:
waiting user sessions (not the agent's own) by wait type, database and
statement (same key as top SQL), with blocking. Sampling-only cycles
reuse the last probe and skip rule evaluation. Stored in
insight.ActiveWaitSamples (migration 0010) for RawDays.

Wait types on the instance page and in the analysis link to
/instanser/{navn}/ventetyper/{type}: Danish explanation from a new
knowledge base (shared with the SQLI-WAIT rules, with Dynamics AX notes),
trend over time, the statements that waited with share and estimated
wait, a link to the full text (/instanser/{navn}/sql) and, for lock
waits, to blocking in the same window. SQL Server 2005 compatible.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:50:31 +02:00
4ba7e5195e Privilege audit: sysadmin and server roles with AD unfolding
Collects members of the fixed server roles and CONTROL SERVER every 15
minutes with login type, disabled state and SQL login password policy.
Windows groups are unfolded recursively through Active Directory on a
Windows agent (cached, 20 s timeout, failures shown with the reason).
Changes since the previous collection are logged, and who/when is read
from the default trace (event 108) when the agent has the optional
ALTER TRACE; authors that only reach the buffered trace later are filled
in at the next collection. The first collection is the baseline.

New rules SQLI-SEC-001 (new privilege, High for sysadmin/securityadmin/
CONTROL SERVER) and SQLI-SEC-002 (more sysadmins than the limit, default
3). Names are sensitive evidence and never in titles or subjects.

Web: /instanser/{navn}/rettigheder and /rettigheder with change log by
period, alert banner on the instance page and overview, and an audit
PDF (/rapporter/rettigheder.pdf) linked from Rapporter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:39:56 +02:00
ab8cfcad97 Configuration page with change log
- new collector instance-config: collation, edition, version, services and
  accounts (sys.dm_server_services, 2008 R2 SP1+), Instant File
  Initialization and Lock Pages in Memory (where the build has the
  columns), tempdb files, default paths; plus a fingerprint of the whole
  setup (sp_configure, trace flags, services, version, tempdb files)
- changes are computed against the previous collection, which the agent
  restores from the repository after a restart; migration 0008 keeps them
  in insight.ConfigurationChanges (as long as resolved findings)
- SQLI-CFG-013 "Konfiguration ændret": information for a day when
  sp_configure, a trace flag or a service account changes (account names
  are sensitive evidence)
- page /instanser/{navn}/konfiguration: instance, CPU and memory, services,
  tempdb, trace flags, non-default sp_configure (default vs. in use vs.
  configured) and the change log; linked from the instance page
- the management report lists configuration changes in the period
- trace flag reading shared by trace-flags and instance-config

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:12:26 +02:00
ac13e3d826 Disk speed per drive: IOPS, MB/s and latency
- file-io records each file's drive: sys.dm_os_volume_stats from 2008 R2 SP1
  (new capability VolumeStats, mount points included), otherwise derived
  from the path (D:\..., \server\share\, / on Linux), so 2005 works too
- migration 0007: Volume and IntervalSeconds on FileIoSamples, and
  DiskHourly (rolled up by maintenance, kept HourlyDays)
- DiskActivity sums files per drive: read/write IOPS, MB/s and latency
  weighted by I/O count
- instance page: "Diskhastighed pr. drev" (table and MB/s + latency charts);
  time window analysis and its PDF: per drive against the same hours of the
  four previous weeks
- SQLI-IO-001/002 now report per drive (one finding when the storage is
  slow, the slowest files in the evidence) instead of per file

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:03:11 +02:00