Skip to content

Commit 33853a1

Browse files
Merge pull request #7 from HasanJaved-Developer/phase-5-webapp
Phase 5 webapp
2 parents 952a5ef + 8b383d3 commit 33853a1

File tree

8 files changed

+253
-65
lines changed

8 files changed

+253
-65
lines changed

ApiIntegrationMvc/ApiIntegrationMvc.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<ItemGroup>
1515
<Folder Include="Areas\Admin\Models\" />
1616
<Folder Include="Areas\Home\Models\" />
17+
<Folder Include="Areas\Ops\Models\" />
18+
<Folder Include="Areas\Ops\Views\" />
1719
<Folder Include="Controllers\" />
1820
</ItemGroup>
1921

ApiIntegrationMvc/Areas/Admin/Controllers/LogsController.cs renamed to ApiIntegrationMvc/Areas/Admin/Controllers/ErrorController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
namespace ApiIntegrationMvc.Areas.Admin.Controllers
55
{
66
[Area("Admin")]
7-
public class LogsController : Controller
7+
public class ErrorController : Controller
88
{
99
private readonly ICentralizedLoggingClient _centralizedlogs;
1010
private readonly IAccessTokenProvider _cache;
1111
private readonly IHttpContextAccessor _http;
12-
public LogsController(ICentralizedLoggingClient centralizedlogs, IAccessTokenProvider cache, IHttpContextAccessor http) => (_centralizedlogs, _cache, _http) = (centralizedlogs, cache, http);
12+
public ErrorController(ICentralizedLoggingClient centralizedlogs, IAccessTokenProvider cache, IHttpContextAccessor http) => (_centralizedlogs, _cache, _http) = (centralizedlogs, cache, http);
1313

1414
public async Task<IActionResult> Index(CancellationToken ct)
1515
{
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
@model IEnumerable<CentralizedLogging.Contracts.Models.GetAllErrorsResponseModel>
2+
@{
3+
ViewData["Title"] = "Error Logs";
4+
}
5+
@section Styles {
6+
7+
<style>
8+
td.dtr-control::before {
9+
content: '+' !important;
10+
font-weight: 700;
11+
display: inline-block;
12+
width: 1.25rem;
13+
text-align: center;
14+
}
15+
16+
tr.dt-hasChild td.dtr-control::before {
17+
content: '' !important;
18+
}
19+
20+
/* ---- Parent-only striping based on DT's stable row classes ---- */
21+
#logsTable.table.table-striped-parents > tbody > tr.odd:not(.child) > * {
22+
background-color: rgba(0,0,0,.03);
23+
}
24+
25+
#logsTable.table.table-striped-parents > tbody > tr.even:not(.child) > * {
26+
background-color: var(--bs-body-bg);
27+
}
28+
29+
/* Parent-only hover */
30+
#logsTable.table.table-hover-parents > tbody > tr:not(.child):hover > * {
31+
background-color: rgba(0,0,0,.05);
32+
}
33+
34+
/* Child rows: always plain (no gray), even after toggles/resizes */
35+
#logsTable.dataTable > tbody > tr.child,
36+
#logsTable.dataTable > tbody > tr.child > td.child {
37+
background-color: var(--bs-body-bg) !important;
38+
--bs-table-accent-bg: transparent !important;
39+
--bs-table-striped-bg: transparent !important;
40+
--bs-table-hover-bg: transparent !important;
41+
--bs-table-active-bg: transparent !important;
42+
}
43+
44+
/* Details list items inside child row */
45+
#logsTable.dataTable > tbody > tr.child ul.dtr-details > li {
46+
background: transparent !important;
47+
border-bottom: 1px solid rgba(0,0,0,.075);
48+
}
49+
50+
51+
</style>
52+
}
53+
54+
<div class="card shadow-sm border-0 rounded-3">
55+
<div class="card-header bg-white">
56+
<h2 class="h5 mb-0 text-primary fw-bold">@ViewData["Title"]</h2>
57+
</div>
58+
59+
<div class="card-body p-0">
60+
<table id="logsTable" class="table table-striped-parents table-hover-parents align-middle dataTable-pending dt-no-gray-child mb-0 w-100">
61+
<thead>
62+
<tr>
63+
<th></th> <!-- control column for + -->
64+
<th data-priority="2">Id</th>
65+
<th data-priority="3">Application</th>
66+
<th data-priority="1">Severity</th>
67+
<th data-priority="1">Message</th>
68+
<th data-priority="5">Source</th>
69+
<th data-priority="6">User</th>
70+
<th data-priority="4">Request</th>
71+
<th data-priority="2">Logged At</th>
72+
</tr>
73+
</thead>
74+
<tbody>
75+
@foreach (var log in Model)
76+
{
77+
var sev = (log.Severity ?? "").Trim();
78+
var sevClass = sev switch
79+
{
80+
"Critical" => "bg-danger",
81+
"Error" => "bg-danger",
82+
"Warning" => "bg-warning text-dark",
83+
"Info" => "bg-primary text-light",
84+
"Debug" => "bg-secondary",
85+
_ => "bg-secondary"
86+
};
87+
88+
<tr>
89+
<td></td> <!-- empty cell for + icon -->
90+
<td class="font-monospace small text-muted">@log.Id</td>
91+
<td class="text-nowrap">@log.ApplicationName</td>
92+
<td><span class="badge @sevClass">@sev</span></td>
93+
<td class="text-wrap" style="max-width:520px; word-break:break-word;" title="@log.Message">@log.Message</td>
94+
<td class="text-nowrap">@log.Source</td>
95+
<td class="text-nowrap">@log.UserId</td>
96+
<td class="font-monospace small text-nowrap">@log.RequestId</td>
97+
<td class="text-nowrap">@log.LoggedAt.ToString("yyyy-MM-dd HH:mm")</td>
98+
</tr>
99+
}
100+
</tbody>
101+
</table>
102+
</div>
103+
</div>
104+
@section Scripts {
105+
106+
107+
<script>
108+
document.addEventListener('DOMContentLoaded', function () {
109+
const dt = new DataTable('#logsTable', {
110+
responsive: {
111+
details: {
112+
type: 'column',
113+
target: 0,
114+
// If you prefer to ALWAYS show all fields in child rows:
115+
// renderer: DataTable.Responsive.renderer.tableAll()
116+
}
117+
},
118+
columnDefs: [
119+
{ targets: 0, className: 'dtr-control', orderable: false },
120+
{ targets: 1, responsivePriority: 1 }, // Id (most important)
121+
{ targets: 3, orderable: false, responsivePriority: 2 }, // Severity
122+
{ targets: 2, responsivePriority: 3 }, // Application
123+
{ targets: 8, responsivePriority: 4 }, // Logged At
124+
{ targets: 7, responsivePriority: 5 }, // Request
125+
{ targets: 4, responsivePriority: 6 }, // Message (hide earlier)
126+
{ targets: 5, responsivePriority: 7 }, // Source
127+
{ targets: 6, responsivePriority: 8 }, // User
128+
{
129+
targets: 4,
130+
render: function (data, type) {
131+
if (type === 'display') {
132+
const div = document.createElement('div');
133+
div.textContent = data ?? '';
134+
const safe = div.innerHTML;
135+
return `<span class="d-inline-block text-wrap" style="max-width:520px" title="${safe}">${safe}</span>`;
136+
}
137+
return data;
138+
}
139+
}
140+
],
141+
paging: true,
142+
pageLength: 25,
143+
lengthMenu: [10, 25, 50, 100],
144+
ordering: true,
145+
searching: true,
146+
info: true,
147+
autoWidth: false, // keep; we’ll call adjust()
148+
deferRender: true
149+
});
150+
151+
// --- KEY FIX: watch the container, not just the window ---
152+
const tableEl = document.querySelector('#logsTable');
153+
const container = tableEl.closest('.card-body') || tableEl.parentElement;
154+
155+
function recalc() {
156+
dt.columns.adjust();
157+
dt.responsive.recalc();
158+
}
159+
160+
// 1) Container resize (sidebar open/close, grid change, etc.)
161+
if ('ResizeObserver' in window && container) {
162+
const ro = new ResizeObserver(() => recalc());
163+
ro.observe(container);
164+
}
165+
166+
// 2) Fallbacks: window resize + Bootstrap “shown” events
167+
const throttled = DataTable.util.throttle(recalc, 100);
168+
window.addEventListener('resize', throttled);
169+
document.addEventListener('shown.bs.tab', throttled);
170+
document.addEventListener('shown.bs.collapse', throttled);
171+
document.addEventListener('shown.bs.offcanvas', throttled);
172+
173+
// 3) If you toggle a custom sidebar, dispatch this event in your code:
174+
// document.dispatchEvent(new Event('sidebar:toggled'));
175+
document.addEventListener('sidebar:toggled', throttled);
176+
177+
// 4) If table was initially hidden (e.g., inside a hidden tab), force a first measure:
178+
setTimeout(recalc, 0);
179+
180+
// Ensure final sizing after fonts/styles
181+
dt.columns.adjust().responsive.recalc();
182+
183+
// Reveal once fully initialized
184+
$('#logsTable').removeClass('dataTable-pending');
185+
186+
187+
188+
});
189+
190+
191+
</script>
192+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace ApiIntegrationMvc.Areas.Ops.Controllers
4+
{
5+
[Area("Ops")]
6+
public class LibraryController : Controller
7+
{
8+
public IActionResult Index()
9+
{
10+
return RedirectToAction("Index", "Home", new { area = "Home" });
11+
}
12+
}
13+
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
2-
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
1+

CentralizedLoggingApi/DbSeeder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static void Seed(IServiceProvider serviceProvider)
1616
if (!context.Applications.Any())
1717
{
1818
context.Applications.AddRange(
19-
new Application { Name = "Payment Service", Environment = "Production", ApiKey = Guid.NewGuid().ToString() },
19+
new Application { Name = "Library Service", Environment = "Production", ApiKey = Guid.NewGuid().ToString() },
2020
new Application { Name = "User Management", Environment = "Staging", ApiKey = Guid.NewGuid().ToString() },
2121
new Application { Name = "Reporting API", Environment = "Development", ApiKey = Guid.NewGuid().ToString() },
2222
new Application { Name = "Integration Portal", Environment = "Development", ApiKey = Guid.NewGuid().ToString() }
@@ -27,7 +27,7 @@ public static void Seed(IServiceProvider serviceProvider)
2727
// Seed ErrorLogs
2828
if (!context.ErrorLogs.Any())
2929
{
30-
var app1 = context.Applications.First(a => a.Name == "Payment Service");
30+
var app1 = context.Applications.First(a => a.Name == "Library Service");
3131
var app2 = context.Applications.First(a => a.Name == "User Management");
3232

3333
context.ErrorLogs.AddRange(
@@ -36,8 +36,8 @@ public static void Seed(IServiceProvider serviceProvider)
3636
ApplicationId = app1.Id,
3737
Severity = "Error",
3838
Message = "Null reference exception in payment processing",
39-
StackTrace = "at PaymentService.Process()...",
40-
Source = "PaymentService",
39+
StackTrace = "at LibraryService.Process()...",
40+
Source = "LibrarytService",
4141
UserId = "user123",
4242
RequestId = Guid.NewGuid().ToString(),
4343
LoggedAt = DateTime.UtcNow

README.md

Lines changed: 36 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A centralized error logging and monitoring API built with **.NET 9**, Entity Framework Core, and SQL Server. This project is designed to serve as a foundation for collecting, storing, and managing error logs from multiple applications. **Phase 5,7&8** have also been complated and the pending **Phase 6** will be developed in a new repository.
44

5-
Each phase is preserved in its own branch, but this repository now focuses on the above highlighted phases i.e. the final **Integration Portal, a Microservices influenced architechture (Single Service Database Pattern)**.
5+
Each phase of development is preserved in its own branch, culminating in the final **Integration Portal,** which, together with the API projects, forms a **Microservices-inspired architechture** that demonstrates **the Single Service Database Pattern.**
66

77
---
88

@@ -14,59 +14,6 @@ A **.NET 9 MVC Web App** that unifies multiple APIs (**User Management and Loggi
1414

1515
---
1616

17-
## 🔑 Key Features
18-
19-
- ✅ A login page with client and server side validations
20-
21-
- ✅ There are two users for login.
22-
23-
- ✅ User name and passwords of the two are alice/alice and bob/bob.
24-
25-
- ✅ Alice is admin and bob is an operations user.
26-
27-
- ✅ Serilog + file sinks implemented for the web app.
28-
29-
- ✅ Invalid user error is posted to the Error logging API
30-
31-
- ✅ When user logins successfully a cotegort tree is generated on the sidebar
32-
33-
- ✅ Only logs link from the side bar work with the logout feature.
34-
35-
- ✅ Admin user can view logs
36-
37-
---
38-
39-
## 📊 Architecture Glimpse
40-
41-
![Integration Portal Architecture](docs/integration_portal_architecture.png)
42-
<sub>[View Mermaid source](docs/integration_portal_architecture.mmd)</sub>
43-
44-
---
45-
46-
### 📸 Screenshots
47-
48-
### 🔑 Login Page
49-
50-
![Login Page](docs/screenshots/login.png)
51-
52-
### 📂 Home & Sidebar
53-
54-
![Sidebar Tree](docs/screenshots/home%20and%20sidebar.png)
55-
56-
### 📊 Logs (Admin View)
57-
58-
![Logs Page](docs/screenshots/logs%20view.png)
59-
60-
## 📡 API Swagger UI
61-
62-
**User Management API**
63-
64-
![User API Swagger](docs/screenshots/usermanagementapi.png)
65-
66-
**Centralized Error Logging API**
67-
68-
![Logging API Swagger](docs/screenshots/centralizedloggingapi.png)
69-
7017
## ⚙️ Implementation Details
7118

7219
- **Login & Validation**
@@ -131,10 +78,45 @@ A **.NET 9 MVC Web App** that unifies multiple APIs (**User Management and Loggi
13178

13279
- Ensures secure re-authentication before accessing APIs again.
13380

81+
---
82+
83+
## 📊 Architecture Glimpse
84+
85+
![Integration Portal Architecture](docs/integration_portal_architecture.png)
86+
<sub>[View Mermaid source](docs/integration_portal_architecture.mmd)</sub>
87+
88+
---
89+
90+
### 📸 Screenshots
91+
92+
### 🔑 Login Page
93+
94+
![Login Page](docs/screenshots/login.png)
95+
96+
### 📂 Home & Sidebar
97+
98+
![Sidebar Tree](docs/screenshots/home%20and%20sidebar.png)
99+
100+
### 📊 Logs (Admin View)
101+
102+
![Logs Page](docs/screenshots/logs%20view.png)
103+
104+
## 📡 API Swagger UI
105+
106+
**User Management API**
107+
108+
![User API Swagger](docs/screenshots/usermanagementapi.png)
109+
110+
**Centralized Error Logging API**
111+
112+
![Logging API Swagger](docs/screenshots/centralizedloggingapi.png)
113+
134114
---
135115

136116
## 🏗️ Planned Enhancements (Next Repository)
137117

118+
119+
138120
The next repository will focus on:
139121

140122
- **Centralized Authorization Layer/Handler**

UserManagementApi/DbSeeder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public static void Seed(IServiceProvider serviceProvider)
2525
// ----- Modules (link via navigation) -----
2626
var modUsers = new Module { Name = "User Management", Area = "Admin", Controller = "Users", Action = "Index", Category = adminCat };
2727
var modRoles = new Module { Name = "Role Management", Area = "Admin", Controller = "Roles", Action = "Index", Category = adminCat };
28-
var modPay = new Module { Name = "Payments", Area = "Ops", Controller = "Payments", Action = "Index", Category = opsCat };
29-
var modLog = new Module { Name = "Logs", Area = "Admin", Controller = "Logs", Action = "Index", Category = adminCat };
28+
var modPay = new Module { Name = "Library", Area = "Ops", Controller = "Library", Action = "Index", Category = opsCat };
29+
var modLog = new Module { Name = "Logs", Area = "Admin", Controller = "Error", Action = "Index", Category = adminCat };
3030
context.Modules.AddRange(modUsers, modRoles, modPay, modLog);
3131
context.SaveChanges();
3232

@@ -35,7 +35,7 @@ public static void Seed(IServiceProvider serviceProvider)
3535
var fUsersEdit = new Function { Module = modUsers, Code = "Users.Edit", DisplayName = "Edit Users" };
3636
var fRolesView = new Function { Module = modRoles, Code = "Roles.View", DisplayName = "View Roles" };
3737
var fRolesAssign = new Function { Module = modRoles, Code = "Roles.Assign", DisplayName = "Assign Roles" };
38-
var fPayView = new Function { Module = modPay, Code = "Payments.View", DisplayName = "View Payments" };
38+
var fPayView = new Function { Module = modPay, Code = "Library.View", DisplayName = "View Library" };
3939
var fLogView = new Function { Module = modLog, Code = "Logs.View", DisplayName = "View Logs" };
4040
context.Functions.AddRange(fUsersView, fUsersEdit, fRolesView, fRolesAssign, fPayView, fLogView);
4141
context.SaveChanges();

0 commit comments

Comments
 (0)