- 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>
57 lines
2.6 KiB
C#
57 lines
2.6 KiB
C#
namespace SQLInsight.Hub;
|
|
|
|
public sealed class HubServerOptions
|
|
{
|
|
public const string SectionName = "SQLInsight:Hub";
|
|
|
|
/// <summary>Public keys trusted for licenses in addition to the embedded ones (key id → base64 SubjectPublicKeyInfo).</summary>
|
|
public IDictionary<string, string> TrustedKeys { get; } = new Dictionary<string, string>();
|
|
|
|
/// <summary>Largest accepted request body.</summary>
|
|
public long MaxRequestBytes { get; set; } = 5 * 1024 * 1024;
|
|
|
|
/// <summary>Most findings accepted per submission.</summary>
|
|
public int MaxFindingsPerSubmission { get; set; } = 2000;
|
|
|
|
/// <summary>Portal login attempts allowed per client IP per minute.</summary>
|
|
public int LoginAttemptsPerMinute { get; set; } = 10;
|
|
|
|
/// <summary>
|
|
/// Folder with the signed update feed (made with the vendor tool's publish command), served at /updates/.
|
|
/// Empty: the hub does not serve updates.
|
|
/// </summary>
|
|
public string? UpdateFeedPath { get; set; }
|
|
|
|
/// <summary>
|
|
/// Folder for the keys that protect login cookies and form tokens. Set it (and keep it) when running in a container
|
|
/// or on more than one server; otherwise staff are logged out on every restart.
|
|
/// </summary>
|
|
public string? DataProtectionKeysPath { get; set; }
|
|
|
|
/// <summary>
|
|
/// IP addresses of reverse proxies in front of the hub. Their X-Forwarded-For/-Proto headers are trusted, so login
|
|
/// rate limiting sees the real client address.
|
|
/// </summary>
|
|
public IList<string> KnownProxies { get; } = [];
|
|
|
|
public RetentionOptions Retention { get; set; } = new();
|
|
|
|
/// <summary>Upgrade insights shared with agents (aggregated, anonymous).</summary>
|
|
public Insights.InsightOptions Insights { get; set; } = new();
|
|
|
|
/// <summary>Support cases from customers: e-mail to SEOSoft when one is created.</summary>
|
|
public Support.SupportOptions Support { get; set; } = new();
|
|
}
|
|
|
|
/// <summary>How long the hub keeps data it no longer needs. Applied once a day.</summary>
|
|
public sealed class RetentionOptions
|
|
{
|
|
/// <summary>Resolved findings are deleted this long after they were resolved (unless linked to an open support case).</summary>
|
|
public TimeSpan ResolvedFindings { get; set; } = TimeSpan.FromDays(365);
|
|
|
|
/// <summary>The submission log (when what was received) is deleted after this.</summary>
|
|
public TimeSpan Submissions { get; set; } = TimeSpan.FromDays(90);
|
|
|
|
/// <summary>Instances that have not reported for this long are deleted with their findings.</summary>
|
|
public TimeSpan SilentInstances { get; set; } = TimeSpan.FromDays(180);
|
|
}
|