-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add alert/notification functionality (for server connection events an…
…d messages)
- 2024.11.29.1-prerelease
- 2024.11.28.1-prerelease
- 2024.11.27.2-prerelease
- 2024.8.16.1-prerelease
- 2024.7.13.1-prerelease
- 2024.7.6.1-prerelease
- 2024.7.4.1-prerelease
- 2024.7.1.1-prerelease
- 2024.6.30.1-prerelease
- 2024.2.12.1-verified
- 2024.2.5.6-prerelease
- 2024.2.5.5-prerelease
- 2023.09.14.2-prerelease
- 2023.09.02.4-prerelease
- 2023.09.02.2-prerelease
- 2023.08.27.3-prerelease
- 2023.08.27.2-prerelease
- 2023.06.10.2-prerelease
- 2023.05.30.2-prerelease
- 2023.05.28.1-prerelease
- 2023.05.15.1-prerelease
- 2023.05.02.2-prerelease
- 2023.04.15.3-prerelease
- 2023.04.09.1-prerelease
- 2023.04.08.3-prerelease
- 2023.03.23.3-prerelease
- 2023.03.23.2-prerelease
- 2023.03.16.1-prerelease
- 2023.01.24.3-prerelease
- 2023.01.24.2-prerelease
- 2023.01.24.1-prerelease
- 2023.01.06.1-prerelease
- 2022.11.04.2-prerelease
- 2022.10.25.2-prerelease
- 2022.10.25.1-prerelease
- 2022.10.24.4-prerelease
- 2022.10.24.3-prerelease
- 2022.10.17.2-prerelease
- 2022.10.13.4-prerelease
- 2022.10.12.1-prerelease
- 2022.09.08.1-prerelease
- 2022.08.26.2-prerelease
- 2022.07.25.1-prerelease
- 2022.07.23.1-prerelease
- 2022.07.20.2-prerelease
- 2022.07.20.1-prerelease
- 2022.07.16.2-prerelease
- 2022.07.13.2-prerelease
- 2022.07.13.1-prerelease
- 2022.07.09.1-prerelease
- 2022.07.06.2-prerelease
- 2022.07.01.1-prerelease
- 2022.06.21.2-prerelease
- 2022.06.21.1-prerelease
- 2022.06.16.4-prerelease
- 2022.06.16.3-prerelease
- 2022.06.16.2-prerelease
- 2022.06.16.1-prerelease
- 2022.06.12.3-prerelease
- 2022.06.12.2-prerelease
- 2022.06.12.1-prerelease
- 2022.06.11.1-prerelease
Showing
16 changed files
with
579 additions
and
21 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System; | ||
using SharedLibraryCore; | ||
using SharedLibraryCore.Alerts; | ||
using SharedLibraryCore.Database.Models; | ||
|
||
namespace IW4MAdmin.Application.Alerts; | ||
|
||
public static class AlertExtensions | ||
{ | ||
public static Alert.AlertState BuildAlert(this EFClient client, Alert.AlertCategory? type = null) | ||
{ | ||
return new Alert.AlertState | ||
{ | ||
RecipientId = client.ClientId, | ||
Category = type ?? Alert.AlertCategory.Information | ||
}; | ||
} | ||
|
||
public static Alert.AlertState WithCategory(this Alert.AlertState state, Alert.AlertCategory category) | ||
{ | ||
state.Category = category; | ||
return state; | ||
} | ||
|
||
public static Alert.AlertState OfType(this Alert.AlertState state, string type) | ||
{ | ||
state.Type = type; | ||
return state; | ||
} | ||
|
||
public static Alert.AlertState WithMessage(this Alert.AlertState state, string message) | ||
{ | ||
state.Message = message; | ||
return state; | ||
} | ||
|
||
public static Alert.AlertState ExpiresIn(this Alert.AlertState state, TimeSpan expiration) | ||
{ | ||
state.ExpiresAt = DateTime.Now.Add(expiration); | ||
return state; | ||
} | ||
|
||
public static Alert.AlertState FromSource(this Alert.AlertState state, string source) | ||
{ | ||
state.Source = source; | ||
return state; | ||
} | ||
|
||
public static Alert.AlertState FromClient(this Alert.AlertState state, EFClient client) | ||
{ | ||
state.Source = client.Name.StripColors(); | ||
state.SourceId = client.ClientId; | ||
return state; | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using SharedLibraryCore.Alerts; | ||
using SharedLibraryCore.Configuration; | ||
using SharedLibraryCore.Database.Models; | ||
using SharedLibraryCore.Interfaces; | ||
|
||
namespace IW4MAdmin.Application.Alerts; | ||
|
||
public class AlertManager : IAlertManager | ||
{ | ||
private readonly ApplicationConfiguration _appConfig; | ||
private readonly ConcurrentDictionary<int, List<Alert.AlertState>> _states = new(); | ||
private readonly List<Func<Task<IEnumerable<Alert.AlertState>>>> _staticSources = new(); | ||
|
||
public AlertManager(ApplicationConfiguration appConfig) | ||
{ | ||
_appConfig = appConfig; | ||
_states.TryAdd(0, new List<Alert.AlertState>()); | ||
} | ||
|
||
public EventHandler<Alert.AlertState> OnAlertConsumed { get; set; } | ||
|
||
public async Task Initialize() | ||
{ | ||
foreach (var source in _staticSources) | ||
{ | ||
var alerts = await source(); | ||
foreach (var alert in alerts) | ||
{ | ||
AddAlert(alert); | ||
} | ||
} | ||
} | ||
|
||
public IEnumerable<Alert.AlertState> RetrieveAlerts(EFClient client) | ||
{ | ||
lock (_states) | ||
{ | ||
var alerts = Enumerable.Empty<Alert.AlertState>(); | ||
if (client.Level > Data.Models.Client.EFClient.Permission.Trusted) | ||
{ | ||
alerts = alerts.Concat(_states[0].Where(alert => | ||
alert.MinimumPermission is null || alert.MinimumPermission <= client.Level)); | ||
} | ||
|
||
if (_states.ContainsKey(client.ClientId)) | ||
{ | ||
alerts = alerts.Concat(_states[client.ClientId].AsReadOnly()); | ||
} | ||
|
||
return alerts.OrderByDescending(alert => alert.OccuredAt); | ||
} | ||
} | ||
|
||
public void MarkAlertAsRead(Guid alertId) | ||
{ | ||
lock (_states) | ||
{ | ||
foreach (var items in _states.Values) | ||
{ | ||
var matchingEvent = items.FirstOrDefault(item => item.AlertId == alertId); | ||
|
||
if (matchingEvent is null) | ||
{ | ||
continue; | ||
} | ||
|
||
items.Remove(matchingEvent); | ||
OnAlertConsumed?.Invoke(this, matchingEvent); | ||
} | ||
} | ||
} | ||
|
||
public void MarkAllAlertsAsRead(int recipientId) | ||
{ | ||
lock (_states) | ||
{ | ||
foreach (var items in _states.Values) | ||
{ | ||
items.RemoveAll(item => | ||
{ | ||
if (item.RecipientId != null && item.RecipientId != recipientId) | ||
{ | ||
return false; | ||
} | ||
|
||
OnAlertConsumed?.Invoke(this, item); | ||
return true; | ||
}); | ||
} | ||
} | ||
} | ||
|
||
public void AddAlert(Alert.AlertState alert) | ||
{ | ||
lock (_states) | ||
{ | ||
if (alert.RecipientId is null) | ||
{ | ||
_states[0].Add(alert); | ||
return; | ||
} | ||
|
||
if (!_states.ContainsKey(alert.RecipientId.Value)) | ||
{ | ||
_states[alert.RecipientId.Value] = new List<Alert.AlertState>(); | ||
} | ||
|
||
if (_appConfig.MinimumAlertPermissions.ContainsKey(alert.Type)) | ||
{ | ||
alert.MinimumPermission = _appConfig.MinimumAlertPermissions[alert.Type]; | ||
} | ||
|
||
_states[alert.RecipientId.Value].Add(alert); | ||
|
||
PruneOldAlerts(); | ||
} | ||
} | ||
|
||
public void RegisterStaticAlertSource(Func<Task<IEnumerable<Alert.AlertState>>> alertSource) | ||
{ | ||
_staticSources.Add(alertSource); | ||
} | ||
|
||
|
||
private void PruneOldAlerts() | ||
{ | ||
foreach (var value in _states.Values) | ||
{ | ||
value.RemoveAll(item => item.ExpiresAt < DateTime.UtcNow); | ||
} | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System; | ||
using Data.Models.Client; | ||
|
||
namespace SharedLibraryCore.Alerts; | ||
|
||
public class Alert | ||
{ | ||
public enum AlertCategory | ||
{ | ||
Information, | ||
Warning, | ||
Error, | ||
Message, | ||
} | ||
|
||
public class AlertState | ||
{ | ||
public Guid AlertId { get; } = Guid.NewGuid(); | ||
public AlertCategory Category { get; set; } | ||
public DateTime OccuredAt { get; set; } = DateTime.UtcNow; | ||
public DateTime? ExpiresAt { get; set; } | ||
public string Message { get; set; } | ||
public string Source { get; set; } | ||
public int? RecipientId { get; set; } | ||
public int? SourceId { get; set; } | ||
public int? ReferenceId { get; set; } | ||
public bool? Delivered { get; set; } | ||
public bool? Consumed { get; set; } | ||
public EFClient.Permission? MinimumPermission { get; set; } | ||
public string Type { get; set; } | ||
public static AlertState Build() => new(); | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using SharedLibraryCore.Alerts; | ||
using SharedLibraryCore.Database.Models; | ||
|
||
namespace SharedLibraryCore.Interfaces; | ||
|
||
public interface IAlertManager | ||
{ | ||
/// <summary> | ||
/// Initializes the manager | ||
/// </summary> | ||
/// <returns></returns> | ||
Task Initialize(); | ||
|
||
/// <summary> | ||
/// Get all the alerts for given client | ||
/// </summary> | ||
/// <param name="client">client to retrieve alerts for</param> | ||
/// <returns></returns> | ||
IEnumerable<Alert.AlertState> RetrieveAlerts(EFClient client); | ||
|
||
/// <summary> | ||
/// Trigger a new alert | ||
/// </summary> | ||
/// <param name="alert">Alert to trigger</param> | ||
void AddAlert(Alert.AlertState alert); | ||
|
||
/// <summary> | ||
/// Marks an alert as read and removes it from the manager | ||
/// </summary> | ||
/// <param name="alertId">Id of the alert to mark as read</param> | ||
void MarkAlertAsRead(Guid alertId); | ||
|
||
/// <summary> | ||
/// Mark all alerts intended for the given recipientId as read | ||
/// </summary> | ||
/// <param name="recipientId">Identifier of the recipient</param> | ||
void MarkAllAlertsAsRead(int recipientId); | ||
|
||
/// <summary> | ||
/// Registers a static (persistent) event source eg datastore that | ||
/// gets initialized at startup | ||
/// </summary> | ||
/// <param name="alertSource">Source action</param> | ||
void RegisterStaticAlertSource(Func<Task<IEnumerable<Alert.AlertState>>> alertSource); | ||
|
||
/// <summary> | ||
/// Fires when an alert has been consumed (dimissed) | ||
/// </summary> | ||
EventHandler<Alert.AlertState> OnAlertConsumed { get; set; } | ||
|
||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
@using SharedLibraryCore.Alerts | ||
@using Humanizer | ||
@model IEnumerable<SharedLibraryCore.Alerts.Alert.AlertState> | ||
@{ | ||
Layout = null; | ||
} | ||
<div class="dropdown with-arrow" data-toggle="dropdown" id="alert-toggle" aria-haspopup="true" aria-expanded="false"> | ||
<div data-toggle="tooltip" data-title="@(Model.Any() ? "View Alerts" : "No Alerts")" data-placement="bottom"> | ||
<i class="oi oi-bell mt-5"></i> | ||
</div> | ||
@if (Model.Any()) | ||
{ | ||
<div class="position-absolute bg-danger rounded-circle ml-10" style="width: 0.5em;height: 0.5em;top: 0;"></div> | ||
<div class="dropdown-menu dropdown-menu-right w-400" aria-labelledby="alert-toggle"> | ||
<div class="d-flex"> | ||
<h6 class="dropdown-header">@ViewBag.Alerts.Count Alerts</h6> | ||
<i class="oi oi-circle-x font-size-12 ml-auto mr-10 text-danger align-self-center profile-action" data-action="DismissAllAlerts" data-action-id="@ViewBag.User.ClientId"></i> | ||
|
||
</div> | ||
<div class="dropdown-divider"></div> | ||
@foreach (var alert in Model) | ||
{ | ||
<div class="d-flex p-5 pl-10 pr-10"> | ||
<div class="align-self-center"> | ||
@if (alert.Category == Alert.AlertCategory.Error) | ||
{ | ||
<i class="oi oi-warning text-danger font-size-12 mr-5"></i> | ||
} | ||
@if (alert.Category == Alert.AlertCategory.Warning) | ||
{ | ||
<i class="oi oi-warning text-secondary font-size-12 mr-5"></i> | ||
} | ||
@if (alert.Category == Alert.AlertCategory.Information) | ||
{ | ||
<i class="oi oi-circle-check font-size-12 mr-5 text-primary"></i> | ||
} | ||
@if (alert.Category == Alert.AlertCategory.Message) | ||
{ | ||
<i class="oi oi-envelope-closed font-size-12 mr-5 text-primary"></i> | ||
} | ||
</div> | ||
<div class="font-size-12 p-5"> | ||
<span>@alert.Message</span> | ||
<div class="text-muted d-flex"> | ||
<span>@alert.OccuredAt.Humanize()</span> | ||
@if (!string.IsNullOrEmpty(alert.Source)) | ||
{ | ||
<span class="ml-5 mr-5">•</span> | ||
@if (alert.SourceId is null) | ||
{ | ||
<div class="text-white font-weight-light">@alert.Source.StripColors()</div> | ||
} | ||
else | ||
{ | ||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@alert.SourceId" class="no-decoration">@alert.Source</a> | ||
} | ||
} | ||
</div> | ||
</div> | ||
<i class="oi oi-circle-x font-size-12 ml-auto align-self-center profile-action" data-action="DismissAlert" data-action-id="@alert.AlertId"></i> | ||
</div> | ||
} | ||
</div> | ||
} | ||
</div> |
This file contains 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