- 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>
119 lines
6.5 KiB
Markdown
119 lines
6.5 KiB
Markdown
# SQLInsight Hub (for SEOSoft)
|
|
|
|
SQLInsight Hub modtager fund fra betalende kunders agenter og samler dem i PostgreSQL. Portalen giver supporten overblik over kunder, kritiske fund, de mest udbredte problemer på tværs af kunder, supportsager og licenser. Hubben kan også servere opdateringsfeedet.
|
|
|
|
Kunder installerer ikke hubben. Den kører hos SEOSoft.
|
|
|
|
## Krav
|
|
|
|
- PostgreSQL 14 eller nyere (testet med 17)
|
|
- .NET 10 runtime (ASP.NET Core), eller Docker
|
|
- En reverse proxy med TLS foran. Agenter sender kun til https-adresser, fordi licensen er deres adgangsnøgle.
|
|
|
|
## Med Docker Compose
|
|
|
|
```bash
|
|
cd deploy/hub
|
|
export HUB_DB_PASSWORD='<langt tilfældigt kodeord>'
|
|
docker compose up -d
|
|
docker compose exec hub dotnet SQLInsight.Hub.dll add-staff <brugernavn> "<fulde navn>" # adgangskode på stdin, mindst 12 tegn
|
|
```
|
|
|
|
Ret `docker-compose.yml`:
|
|
|
|
- `SQLInsight__Hub__TrustedKeys__<kid>`: den offentlige nøgle, licenserne er signeret med (fra `keygen`).
|
|
- `SQLInsight__Hub__KnownProxies__0`: reverse proxyens adresse, så login-begrænsningen ser klientens rigtige IP.
|
|
|
|
Volumen `hubdata` indeholder nøglerne til login-cookies (`/data/keys`) og opdateringsfeedet (`/data/feed`). Tag backup af den og af PostgreSQL.
|
|
|
|
## Uden Docker
|
|
|
|
```bash
|
|
dotnet publish src/SQLInsight.Hub -c Release -o /opt/sqlinsight/hub
|
|
```
|
|
|
|
`appsettings.json`:
|
|
|
|
```json
|
|
{
|
|
"ConnectionStrings": { "Hub": "Host=db01;Database=sqlinsight_hub;Username=sqlinsight_hub;Password=…" },
|
|
"SQLInsight": {
|
|
"Hub": {
|
|
"TrustedKeys": { "k20260929": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE…" },
|
|
"DataProtectionKeysPath": "/var/lib/sqlinsight-hub/keys",
|
|
"UpdateFeedPath": "/var/lib/sqlinsight-hub/feed",
|
|
"KnownProxies": [ "10.0.0.5" ],
|
|
"Retention": { "ResolvedFindings": "365.00:00:00", "Submissions": "90.00:00:00", "SilentInstances": "180.00:00:00" }
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Hubben kan køre som Windows-tjeneste eller systemd-enhed (`Type=notify`) på samme måde som agenten. Databaseskemaet oprettes og opgraderes ved start.
|
|
|
|
## Erfaring på tværs af kunder
|
|
|
|
Hubben registrerer, når en instans skifter build, og beregner, hvilke fund der forsvinder inden for 3 dage efter en opdatering, og hvilke der stadig findes efter 7 dage. Under **Opgraderinger** i portalen ses alle sammenhænge, også dem med få kunder. Sammenhænge med mindst 3 kunder, hvor fundet oftest forsvandt, hentes af agenterne (`GET /api/v1/insights`, med licens) og vises på fundet i kundens webbrugerflade.
|
|
|
|
```json
|
|
"Insights": { "MinCustomers": 3, "ResolvedWithin": "3.00:00:00", "PersistedAfter": "7.00:00:00" }
|
|
```
|
|
|
|
Regler om selve versionen (SQLI-VER-*) er udeladt, fordi de altid forsvinder ved en opdatering.
|
|
|
|
## Supportsager
|
|
|
|
**Sagsnumre** tildeles pr. kunde: `SUP-000001`, `SUP-000002` … Portalen foreslår det næste nummer, når en sag oprettes fra et fund eller under **Ny sag** på kundens side. Feltet kan rettes (fx til et nummer fra et andet system), men et nummer kan kun bruges én gang pr. kunde. Håndskrevne numre, der ikke har formen `SUP-<tal>`, påvirker ikke rækkefølgen.
|
|
|
|
**Kundens egne sager:** betalende kunder (Professional/Enterprise) opretter sager i SQLInsight Web (se [support.md](support.md)). Agenten sender dem til `POST /api/v1/cases` med licensen og henter status for kundens sager med `GET /api/v1/cases`. Kundeoprettede sager er markeret *Kunde* under **Supportsager** med kontaktperson og beskrivelse, og er knyttet til fundet, hvis sagen blev oprettet fra et.
|
|
|
|
**Mail til support,** når en kunde opretter en sag (valgfri). Uden indstillingerne vises nye sager kun i portalen. Med Docker Compose/Forgejo sættes de som repo-secrets (`SUPPORT_SMTP_HOST`, `SUPPORT_SMTP_PORT`, `SUPPORT_SMTP_USER`, `SUPPORT_SMTP_PASSWORD`, `SUPPORT_SMTP_FROM`, `SUPPORT_SMTP_TO` kommasepareret, `SUPPORT_PORTAL_URL`), ellers i `appsettings.json`:
|
|
|
|
```json
|
|
"Support": {
|
|
"Notify": {
|
|
"Enabled": true, "SmtpHost": "smtp.office365.com", "SmtpPort": 587, "StartTls": true,
|
|
"UserName": "support@seosoft.dk", "Password": "…", "From": "support@seosoft.dk",
|
|
"To": [ "support@seosoft.dk" ], "PortalUrl": "https://hub.sqlinsight.seosoft.dk/"
|
|
}
|
|
}
|
|
```
|
|
|
|
Microsoft 365 kræver, at *Authenticated SMTP* er slået til for postkassen; hubben logger ind med AUTH LOGIN. Svar på mailen går til kundens kontaktperson. En mail, der ikke kan sendes, logges; sagen er gemt alligevel.
|
|
|
|
**Supportrapport** (portalen → **Supportrapport**): for en periode (standard de seneste 90 dage) og alle kunder eller én: oprettede, lukkede og åbne sager, gennemsnitlig og median løsningstid (fra oprettelse til lukning), ældste åbne sag og andelen, kunden selv har oprettet, samt alle sager i perioden. **Hent CSV** giver listen til Excel (UTF-8, semikolon).
|
|
|
|
## Licenser
|
|
|
|
```bash
|
|
# Én gang: nøglepar. Den private nøgle forlader aldrig SEOSofts maskine og må aldrig i git.
|
|
dotnet run --project tools/SQLInsight.LicenseTool -- keygen --out ~/sqlinsight-keys
|
|
|
|
# Pr. kunde
|
|
dotnet run --project tools/SQLInsight.LicenseTool -- issue --key ~/sqlinsight-keys/sqlinsight-k20260929.private.pem \
|
|
--kid k20260929 --customer "Kunde A/S" --plan Professional --expires 2027-09-30 --instances 10
|
|
```
|
|
|
|
Den offentlige nøgle lægges i `src/SQLInsight.Licensing/trusted-keys.json` før en release, så agenterne kan verificere licensen offline. Hubben får den via `TrustedKeys`. Licenser kan spærres i portalen under **Licenser**. Agenten får så 403 ved næste afsendelse.
|
|
|
|
## Opbevaring og sletning
|
|
|
|
Hver nat sletter hubben:
|
|
|
|
| Data | Efter |
|
|
|---|---|
|
|
| Løste fund (medmindre de hører til en åben supportsag) | `Retention:ResolvedFindings`, 365 dage |
|
|
| Log over modtagelser | `Retention:Submissions`, 90 dage |
|
|
| Instanser, der ikke har rapporteret, med deres fund | `Retention:SilentInstances`, 180 dage |
|
|
|
|
Når en aftale ophører, eller en kunde beder om sletning:
|
|
|
|
```bash
|
|
dotnet SQLInsight.Hub.dll delete-customer "Kunde A/S" --confirm
|
|
```
|
|
|
|
Alle kundens instanser, fund, regelstatus, modtagelser og supportsager slettes. Licenserne spærres, så agenterne ikke kan sende igen, og kunden omdøbes til "Slettet kunde #id". Se også [databehandling.md](databehandling.md).
|
|
|
|
### Kunden lukker en sag
|
|
|
|
`POST /api/v1/cases/{reference}/close` (licens som Authorization, kræver support i licensen) lukker en af licensens egne sager; andres sager giver 404. Body `{ comment?, requestedBy? }`. Sagen får `ClosedBy = "Kunde: <bruger>"` og kommentaren; lukning i portalen giver `ClosedBy = "SEOSoft: <medarbejder>"`. Kaldet er idempotent. Agenten sender lukninger, som kunden har bedt om i SQLInsight Web, og en senere liste fra hubben sætter ikke en ventende lukning tilbage til åben.
|