- Case numbers SUP-000001, SUP-000002 … per customer, unique per customer (new index; a migration step renames any old duplicates first). The portal suggests the next number on the finding page and under the new "Ny sag" on the customer page; an empty field gets the next number, a typed one that is taken is refused. Concurrent creates retry. - SupportCase gains Source (Staff/Customer), description, contact name, e-mail, phone, the customer's Windows user and ClientRequestId. - POST/GET /api/v1/cases for the customer's agent (license with priority support): creates a case idempotently on ClientRequestId, links the finding it came from, lists the customer's cases incl. staff-created. Customer/license creation is shared with the findings endpoint. - Optional mail to support for customer cases (MailKit; prefers AUTH LOGIN as Microsoft 365 needs). A failed mail is logged, never fails the call. deploy-hub.yml writes optional SUPPORT_SMTP_* secrets to .env. - Supportrapport: created, closed, open now, average/median resolution time, oldest open case and customer-created share per customer for a period (default 90 days), plus the cases; CSV for Danish Excel. - Cases page: newest first, customer cases marked, contact/description. - Customer page: findings can be filtered to one instance (?instans=id, ignored for another customer's instance); active findings per instance by severity. - dotnet-ef as a local tool (dotnet-tools.json) for the migration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
115 lines
6 KiB
Markdown
115 lines
6 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).
|