Skip to content

Commit 65cbd47

Browse files
Copilotmo-esmp
andcommitted
Add backend dashboard API implementation
Co-authored-by: mo-esmp <[email protected]>
1 parent 8353e53 commit 65cbd47

File tree

14 files changed

+362
-12
lines changed

14 files changed

+362
-12
lines changed

src/Serilog.Ui.Core/AggregateDataProvider.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,9 @@ public AggregateDataProvider(IEnumerable<IDataProvider> dataProviders)
7575
/// <inheritdoc/>
7676
public Task<(IEnumerable<LogModel>, int)> FetchDataAsync(FetchLogsQuery queryParams, CancellationToken cancellationToken = default)
7777
=> SelectedDataProvider.FetchDataAsync(queryParams, cancellationToken);
78+
79+
/// <inheritdoc/>
80+
public Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
81+
=> SelectedDataProvider.FetchDashboardAsync(cancellationToken);
7882
}
7983
}

src/Serilog.Ui.Core/IDataProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ public interface IDataProvider
1515
/// </summary>
1616
Task<(IEnumerable<LogModel> results, int total)> FetchDataAsync(FetchLogsQuery queryParams, CancellationToken cancellationToken = default);
1717

18+
/// <summary>
19+
/// Fetches dashboard statistics asynchronous.
20+
/// </summary>
21+
Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default);
22+
1823
/// <summary>
1924
/// Name of the provider, used to identify this provider when using multiple.
2025
/// </summary>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
3+
namespace Serilog.Ui.Core.Models
4+
{
5+
/// <summary>
6+
/// Represents dashboard statistics for log data visualization.
7+
/// </summary>
8+
public class DashboardModel
9+
{
10+
/// <summary>
11+
/// Gets or sets the total count of logs.
12+
/// </summary>
13+
public int TotalLogs { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the count of logs by level.
17+
/// </summary>
18+
public Dictionary<string, int> LogsByLevel { get; set; } = new();
19+
20+
/// <summary>
21+
/// Gets or sets the count of logs for today.
22+
/// </summary>
23+
public int TodayLogs { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the count of error logs for today.
27+
/// </summary>
28+
public int TodayErrorLogs { get; set; }
29+
}
30+
}

src/Serilog.Ui.ElasticSearchProvider/ElasticSearchDbDataProvider.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,54 @@ public class ElasticSearchDbDataProvider(IElasticClient client, ElasticSearchDbO
5656

5757
return (result?.Documents.Select((x, index) => x.ToLogModel(rowNoStart, index)).ToList() ?? [], total);
5858
}
59+
60+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
61+
{
62+
var dashboard = new DashboardModel();
63+
var today = DateTime.Today;
64+
var tomorrow = today.AddDays(1);
65+
66+
// Get total logs count
67+
var totalResponse = await _client.CountAsync<ElasticSearchDbLogModel>(c => c
68+
.Index(options.IndexName), cancellationToken);
69+
dashboard.TotalLogs = (int)(totalResponse?.Count ?? 0);
70+
71+
// Get logs count by level
72+
var levelResponse = await _client.SearchAsync<ElasticSearchDbLogModel>(s => s
73+
.Index(options.IndexName)
74+
.Size(0)
75+
.Aggregations(aggs => aggs
76+
.Terms("levels", t => t.Field(f => f.Level))
77+
), cancellationToken);
78+
79+
if (levelResponse?.Aggregations?.Terms("levels") is { } levelsAgg)
80+
{
81+
dashboard.LogsByLevel = levelsAgg.Buckets.ToDictionary(
82+
bucket => bucket.Key.ToString() ?? "Unknown",
83+
bucket => (int)bucket.DocCount);
84+
}
85+
86+
// Get today's logs count
87+
var todayResponse = await _client.CountAsync<ElasticSearchDbLogModel>(c => c
88+
.Index(options.IndexName)
89+
.Query(q => q
90+
.DateRange(r => r.Field(f => f.Timestamp).GreaterThanOrEquals(today).LessThan(tomorrow))
91+
), cancellationToken);
92+
dashboard.TodayLogs = (int)(todayResponse?.Count ?? 0);
93+
94+
// Get today's error logs count
95+
var todayErrorResponse = await _client.CountAsync<ElasticSearchDbLogModel>(c => c
96+
.Index(options.IndexName)
97+
.Query(q => q
98+
.Bool(b => b
99+
.Must(
100+
m => m.Term(t => t.Field(f => f.Level).Value("Error")),
101+
m => m.DateRange(r => r.Field(f => f.Timestamp).GreaterThanOrEquals(today).LessThan(tomorrow))
102+
)
103+
)
104+
), cancellationToken);
105+
dashboard.TodayErrorLogs = (int)(todayErrorResponse?.Count ?? 0);
106+
107+
return dashboard;
108+
}
59109
}

src/Serilog.Ui.MongoDbProvider/MongoDbDataProvider.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,38 @@ private static SortDefinition<MongoDbLogModel> GenerateSortClause(SortProperty s
121121

122122
return isDesc ? Builders<MongoDbLogModel>.Sort.Descending(sortPropertyName) : Builders<MongoDbLogModel>.Sort.Ascending(sortPropertyName);
123123
}
124+
125+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
126+
{
127+
var dashboard = new DashboardModel();
128+
var today = DateTime.Today.ToUniversalTime();
129+
var tomorrow = today.AddDays(1);
130+
131+
// Get total logs count
132+
dashboard.TotalLogs = Convert.ToInt32(await _collection.CountDocumentsAsync(Builders<MongoDbLogModel>.Filter.Empty, cancellationToken: cancellationToken));
133+
134+
// Get logs count by level
135+
var levelCounts = await _collection.Aggregate()
136+
.Group(x => x.Level, g => new { Level = g.Key, Count = g.Count() })
137+
.ToListAsync(cancellationToken);
138+
dashboard.LogsByLevel = levelCounts.ToDictionary(x => x.Level ?? "Unknown", x => x.Count);
139+
140+
// Get today's logs count
141+
var todayFilter = Builders<MongoDbLogModel>.Filter.And(
142+
Builders<MongoDbLogModel>.Filter.Gte(x => x.UtcTimeStamp, today),
143+
Builders<MongoDbLogModel>.Filter.Lt(x => x.UtcTimeStamp, tomorrow)
144+
);
145+
dashboard.TodayLogs = Convert.ToInt32(await _collection.CountDocumentsAsync(todayFilter, cancellationToken: cancellationToken));
146+
147+
// Get today's error logs count
148+
var todayErrorFilter = Builders<MongoDbLogModel>.Filter.And(
149+
Builders<MongoDbLogModel>.Filter.Eq(x => x.Level, "Error"),
150+
Builders<MongoDbLogModel>.Filter.Gte(x => x.UtcTimeStamp, today),
151+
Builders<MongoDbLogModel>.Filter.Lt(x => x.UtcTimeStamp, tomorrow)
152+
);
153+
dashboard.TodayErrorLogs = Convert.ToInt32(await _collection.CountDocumentsAsync(todayErrorFilter, cancellationToken: cancellationToken));
154+
155+
return dashboard;
156+
}
124157
}
125158
}

src/Serilog.Ui.MsSqlServerProvider/SqlServerDataProvider.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Serilog.Ui.Core;
44
using Serilog.Ui.Core.Models;
55
using Serilog.Ui.MsSqlServerProvider.Extensions;
6+
using System;
67
using System.Collections.Generic;
78
using System.Data;
89
using System.Linq;
@@ -73,4 +74,40 @@ private async Task<int> CountLogsAsync(FetchLogsQuery queryParams)
7374
queryParams.EndDate
7475
});
7576
}
77+
78+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
79+
{
80+
var dashboard = new DashboardModel();
81+
var today = DateTime.Today;
82+
var tomorrow = today.AddDays(1);
83+
84+
using IDbConnection connection = new SqlConnection(options.ConnectionString);
85+
86+
// Get total logs count
87+
var totalQuery = $"SELECT COUNT(*) FROM [{options.Schema}].[{options.TableName}]";
88+
dashboard.TotalLogs = await connection.QueryFirstOrDefaultAsync<int>(totalQuery);
89+
90+
// Get logs count by level
91+
var levelQuery = $"SELECT [{options.ColumnNames.Level}] as Level, COUNT(*) as Count FROM [{options.Schema}].[{options.TableName}] GROUP BY [{options.ColumnNames.Level}]";
92+
var levelCounts = await connection.QueryAsync<(string Level, int Count)>(levelQuery);
93+
dashboard.LogsByLevel = levelCounts.ToDictionary(x => x.Level ?? "Unknown", x => x.Count);
94+
95+
// Get today's logs count
96+
var todayQuery = $"SELECT COUNT(*) FROM [{options.Schema}].[{options.TableName}] WHERE [{options.ColumnNames.Timestamp}] >= @StartDate AND [{options.ColumnNames.Timestamp}] < @EndDate";
97+
dashboard.TodayLogs = await connection.QueryFirstOrDefaultAsync<int>(todayQuery, new
98+
{
99+
StartDate = today,
100+
EndDate = tomorrow
101+
});
102+
103+
// Get today's error logs count
104+
var todayErrorQuery = $"SELECT COUNT(*) FROM [{options.Schema}].[{options.TableName}] WHERE [{options.ColumnNames.Level}] = 'Error' AND [{options.ColumnNames.Timestamp}] >= @StartDate AND [{options.ColumnNames.Timestamp}] < @EndDate";
105+
dashboard.TodayErrorLogs = await connection.QueryFirstOrDefaultAsync<int>(todayErrorQuery, new
106+
{
107+
StartDate = today,
108+
EndDate = tomorrow
109+
});
110+
111+
return dashboard;
112+
}
76113
}

src/Serilog.Ui.MySqlProvider/Shared/DataProvider.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,40 @@ private async Task<int> CountLogsAsync(FetchLogsQuery queryParams)
7272
queryParams.EndDate
7373
});
7474
}
75+
76+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
77+
{
78+
var dashboard = new DashboardModel();
79+
var today = DateTime.Today;
80+
var tomorrow = today.AddDays(1);
81+
82+
using MySqlConnection connection = new(options.ConnectionString);
83+
84+
// Get total logs count
85+
var totalQuery = $"SELECT COUNT(*) FROM `{options.Schema}`.`{options.TableName}`";
86+
dashboard.TotalLogs = await connection.QueryFirstOrDefaultAsync<int>(totalQuery);
87+
88+
// Get logs count by level
89+
var levelQuery = $"SELECT `{options.ColumnNames.Level}` as Level, COUNT(*) as Count FROM `{options.Schema}`.`{options.TableName}` GROUP BY `{options.ColumnNames.Level}`";
90+
var levelCounts = await connection.QueryAsync<(string Level, int Count)>(levelQuery);
91+
dashboard.LogsByLevel = levelCounts.ToDictionary(x => x.Level ?? "Unknown", x => x.Count);
92+
93+
// Get today's logs count
94+
var todayQuery = $"SELECT COUNT(*) FROM `{options.Schema}`.`{options.TableName}` WHERE `{options.ColumnNames.Timestamp}` >= @StartDate AND `{options.ColumnNames.Timestamp}` < @EndDate";
95+
dashboard.TodayLogs = await connection.QueryFirstOrDefaultAsync<int>(todayQuery, new
96+
{
97+
StartDate = today,
98+
EndDate = tomorrow
99+
});
100+
101+
// Get today's error logs count
102+
var todayErrorQuery = $"SELECT COUNT(*) FROM `{options.Schema}`.`{options.TableName}` WHERE `{options.ColumnNames.Level}` = 'Error' AND `{options.ColumnNames.Timestamp}` >= @StartDate AND `{options.ColumnNames.Timestamp}` < @EndDate";
103+
dashboard.TodayErrorLogs = await connection.QueryFirstOrDefaultAsync<int>(todayErrorQuery, new
104+
{
105+
StartDate = today,
106+
EndDate = tomorrow
107+
});
108+
109+
return dashboard;
110+
}
75111
}

src/Serilog.Ui.PostgreSqlProvider/PostgresDataProvider.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,42 @@ private async Task<int> CountLogsAsync(FetchLogsQuery queryParams)
7979
queryParams.EndDate
8080
});
8181
}
82+
83+
/// <inheritdoc/>
84+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
85+
{
86+
var dashboard = new DashboardModel();
87+
var today = System.DateTime.Today;
88+
var tomorrow = today.AddDays(1);
89+
90+
await using NpgsqlConnection connection = new(options.ConnectionString);
91+
92+
// Get total logs count
93+
var totalQuery = $"SELECT COUNT(*) FROM \"{options.Schema}\".\"{options.TableName}\"";
94+
dashboard.TotalLogs = await connection.QueryFirstOrDefaultAsync<int>(totalQuery);
95+
96+
// Get logs count by level
97+
var levelQuery = $"SELECT {options.ColumnNames.Level} as Level, COUNT(*) as Count FROM \"{options.Schema}\".\"{options.TableName}\" GROUP BY {options.ColumnNames.Level}";
98+
var levelCounts = await connection.QueryAsync<(int Level, int Count)>(levelQuery);
99+
dashboard.LogsByLevel = levelCounts.ToDictionary(x => LogLevelConverter.GetLevelName(x.Level.ToString()), x => x.Count);
100+
101+
// Get today's logs count
102+
var todayQuery = $"SELECT COUNT(*) FROM \"{options.Schema}\".\"{options.TableName}\" WHERE \"{options.ColumnNames.Timestamp}\" >= @StartDate AND \"{options.ColumnNames.Timestamp}\" < @EndDate";
103+
dashboard.TodayLogs = await connection.QueryFirstOrDefaultAsync<int>(todayQuery, new
104+
{
105+
StartDate = today,
106+
EndDate = tomorrow
107+
});
108+
109+
// Get today's error logs count (Error level = 3 in PostgreSQL)
110+
var todayErrorQuery = $"SELECT COUNT(*) FROM \"{options.Schema}\".\"{options.TableName}\" WHERE {options.ColumnNames.Level} = @ErrorLevel AND \"{options.ColumnNames.Timestamp}\" >= @StartDate AND \"{options.ColumnNames.Timestamp}\" < @EndDate";
111+
dashboard.TodayErrorLogs = await connection.QueryFirstOrDefaultAsync<int>(todayErrorQuery, new
112+
{
113+
ErrorLevel = LogLevelConverter.GetLevelValue("Error"),
114+
StartDate = today,
115+
EndDate = tomorrow
116+
});
117+
118+
return dashboard;
119+
}
82120
}

src/Serilog.Ui.RavenDbProvider/RavenDbDataProvider.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,35 @@ SortDirection sortBy
105105
_ => query.OrderByDescending(q => q.Timestamp),
106106
};
107107
}
108+
109+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
110+
{
111+
var dashboard = new DashboardModel();
112+
var today = DateTime.Today;
113+
var tomorrow = today.AddDays(1);
114+
115+
using var session = _documentStore.OpenAsyncSession();
116+
117+
// Get total logs count
118+
dashboard.TotalLogs = await session.Query<RavenDbLogModel>().CountAsync();
119+
120+
// Get logs count by level
121+
var levelCounts = await session.Query<RavenDbLogModel>()
122+
.GroupBy(x => x.Level)
123+
.Select(g => new { Level = g.Key, Count = g.Count() })
124+
.ToListAsync();
125+
dashboard.LogsByLevel = levelCounts.ToDictionary(x => x.Level ?? "Unknown", x => x.Count);
126+
127+
// Get today's logs count
128+
dashboard.TodayLogs = await session.Query<RavenDbLogModel>()
129+
.Where(x => x.Timestamp >= today && x.Timestamp < tomorrow)
130+
.CountAsync();
131+
132+
// Get today's error logs count
133+
dashboard.TodayErrorLogs = await session.Query<RavenDbLogModel>()
134+
.Where(x => x.Level == "Error" && x.Timestamp >= today && x.Timestamp < tomorrow)
135+
.CountAsync();
136+
137+
return dashboard;
138+
}
108139
}

src/Serilog.Ui.SqliteDataProvider/SqliteDataProvider.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,42 @@ public class SqliteDataProvider(SqliteDbOptions options, SqliteQueryBuilder quer
3131

3232
public string Name => _options.GetProviderName(SqliteProviderName);
3333

34+
public async Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
35+
{
36+
var dashboard = new DashboardModel();
37+
var today = DateTime.Today;
38+
var tomorrow = today.AddDays(1);
39+
40+
using var connection = new SqliteConnection(_options.ConnectionString);
41+
42+
// Get total logs count
43+
var totalQuery = $"SELECT COUNT(*) FROM {_options.TableName}";
44+
dashboard.TotalLogs = await connection.QueryFirstOrDefaultAsync<int>(totalQuery);
45+
46+
// Get logs count by level
47+
var levelQuery = $"SELECT {_options.ColumnNames.Level} as Level, COUNT(*) as Count FROM {_options.TableName} GROUP BY {_options.ColumnNames.Level}";
48+
var levelCounts = await connection.QueryAsync<(string Level, int Count)>(levelQuery);
49+
dashboard.LogsByLevel = levelCounts.ToDictionary(x => x.Level ?? "Unknown", x => x.Count);
50+
51+
// Get today's logs count
52+
var todayQuery = $"SELECT COUNT(*) FROM {_options.TableName} WHERE {_options.ColumnNames.Timestamp} >= @StartDate AND {_options.ColumnNames.Timestamp} < @EndDate";
53+
dashboard.TodayLogs = await connection.QueryFirstOrDefaultAsync<int>(todayQuery, new
54+
{
55+
StartDate = StringifyDate(today),
56+
EndDate = StringifyDate(tomorrow)
57+
});
58+
59+
// Get today's error logs count
60+
var todayErrorQuery = $"SELECT COUNT(*) FROM {_options.TableName} WHERE {_options.ColumnNames.Level} = 'Error' AND {_options.ColumnNames.Timestamp} >= @StartDate AND {_options.ColumnNames.Timestamp} < @EndDate";
61+
dashboard.TodayErrorLogs = await connection.QueryFirstOrDefaultAsync<int>(todayErrorQuery, new
62+
{
63+
StartDate = StringifyDate(today),
64+
EndDate = StringifyDate(tomorrow)
65+
});
66+
67+
return dashboard;
68+
}
69+
3470
private async Task<IEnumerable<LogModel>> GetLogsAsync(FetchLogsQuery queryParams)
3571
{
3672
var query = queryBuilder.BuildFetchLogsQuery(_options.ColumnNames, _options.Schema, _options.TableName, queryParams);

0 commit comments

Comments
 (0)