Merge pull request 'feature/db-check' (#21) from feature/db-check into development
All checks were successful
Development Build / build-and-test (push) Successful in 54s
Development Build / docker-build-and-push (push) Successful in 6m7s

Reviewed-on: #21
This commit is contained in:
2025-10-03 13:11:17 +02:00
11 changed files with 134 additions and 34 deletions

View File

@@ -10,11 +10,12 @@ env:
DOCKER_IMAGE_NAME: 'watcher-server'
REGISTRY_URL: 'git.triggermeelmo.com/watcher'
DOCKER_PLATFORMS: 'linux/amd64,linux/arm64'
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
jobs:
build-and-test:
runs-on: ubuntu-latest
env:
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
steps:
- name: Checkout code
uses: actions/checkout@v3
@@ -39,6 +40,8 @@ jobs:
docker-build-and-push:
runs-on: ubuntu-latest
env:
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
needs: build-and-test
steps:
- name: Checkout code

View File

@@ -19,14 +19,14 @@ namespace Watcher.Controllers
private readonly ILogger<HomeController> _logger;
// Daten der Backgroundchecks abrufen
private INetworkCheckStore _NetworkCheckStore;
private IDashboardStore _DashboardStore;
// HomeController Constructor
public HomeController(AppDbContext context, ILogger<HomeController> logger, INetworkCheckStore NetworkCheckStore)
public HomeController(AppDbContext context, ILogger<HomeController> logger, IDashboardStore dashboardStore)
{
_context = context;
_logger = logger;
_NetworkCheckStore = NetworkCheckStore;
_DashboardStore = dashboardStore;
}
@@ -41,7 +41,6 @@ namespace Watcher.Controllers
.Where(u => u.Username == preferredUserName)
.FirstOrDefaultAsync();
Console.WriteLine("Index" + _NetworkCheckStore.NetworkStatus);
var viewModel = new DashboardViewModel
{
ActiveServers = await _context.Servers.CountAsync(s => s.IsOnline),
@@ -59,7 +58,8 @@ namespace Watcher.Controllers
Containers = await _context.Containers
.OrderBy(s => s.Name)
.ToListAsync(),
NetworkStatus = _NetworkCheckStore.NetworkStatus
NetworkStatus = _DashboardStore.NetworkStatus,
DatabaseStatus = _DashboardStore.DatabaseStatus
};
//ViewBag.NetworkConnection = _NetworkCheckStore.NetworkStatus;
return View(viewModel);
@@ -74,8 +74,6 @@ namespace Watcher.Controllers
var now = DateTime.UtcNow;
Console.WriteLine("DashboardStats" + _NetworkCheckStore.NetworkStatus);
var model = new DashboardViewModel
{
ActiveServers = await _context.Servers.CountAsync(s => s.IsOnline),
@@ -92,7 +90,8 @@ namespace Watcher.Controllers
Containers = await _context.Containers
.OrderBy(s => s.Name)
.ToListAsync(),
NetworkStatus = _NetworkCheckStore.NetworkStatus
NetworkStatus = _DashboardStore.NetworkStatus,
DatabaseStatus = _DashboardStore.DatabaseStatus
};
return PartialView("_DashboardStats", model);

View File

@@ -35,10 +35,11 @@ builder.Services.AddControllersWithViews();
builder.Services.AddHttpContextAccessor();
// Storage Singleton
builder.Services.AddSingleton<INetworkCheckStore, NetworkCheckStore>();
builder.Services.AddSingleton<IDashboardStore, DashboardStore>();
// Background Services
builder.Services.AddHostedService<NetworkCheck>();
builder.Services.AddHostedService<DatabaseCheck>();
// ---------- Konfiguration ----------

View File

@@ -0,0 +1,8 @@
namespace Watcher.Services;
public class DashboardStore : IDashboardStore
{
public String? NetworkStatus { get; set; }
public String? DatabaseStatus { get; set; }
}

View File

@@ -0,0 +1,67 @@
using Microsoft.Data.Sqlite;
namespace Watcher.Services;
public class DatabaseCheck : BackgroundService
{
private readonly ILogger<DatabaseCheck> _logger;
private IDashboardStore _dashboardStore;
public DatabaseCheck(ILogger<DatabaseCheck> logger, IDashboardStore dashboardStore)
{
_logger = logger;
_dashboardStore = dashboardStore;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// Hintergrundprozess abwarten
await checkDatabaseIntegrity();
// 5 Sekdunden Offset
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
// String sqliteConnectionString als Argument übergeben
public Task checkDatabaseIntegrity()
{
using var conn = new SqliteConnection("Data Source=./persistence/watcher.db");
_logger.LogInformation("Sqlite Integrity-Check started...");
try
{
conn.Open();
using var command = conn.CreateCommand();
command.CommandText = """
SELECT integrity_check FROM pragma_integrity_check;
""";
using var reader = command.ExecuteReader();
while (reader.Read())
{
string status = reader.GetString(0);
_dashboardStore.DatabaseStatus = status;
_logger.LogInformation("Sqlite DatabaseIntegrity: ${status}", status);
}
conn.Close();
}
catch (SqliteException e)
{
conn.Close();
_logger.LogError(e.Message);
// TODO: LogEvent erstellen
}
_logger.LogInformation("Database Integrity-Check finished.");
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,7 @@
namespace Watcher.Services;
public interface IDashboardStore
{
String? NetworkStatus { get; set; }
String? DatabaseStatus { get; set; }
}

View File

@@ -1,6 +0,0 @@
namespace Watcher.Services;
public interface INetworkCheckStore
{
String? NetworkStatus { get; set; }
}

View File

@@ -1,4 +1,3 @@
using System.Composition;
using System.Net.NetworkInformation;
namespace Watcher.Services;
@@ -6,12 +5,12 @@ public class NetworkCheck : BackgroundService
{
private readonly ILogger<NetworkCheck> _logger;
private INetworkCheckStore _NetworkCheckStore;
private IDashboardStore _DashboardStore;
public NetworkCheck(ILogger<NetworkCheck> logger, INetworkCheckStore NetworkCheckStore)
public NetworkCheck(ILogger<NetworkCheck> logger, IDashboardStore DashboardStore)
{
_logger = logger;
_NetworkCheckStore = NetworkCheckStore;
_DashboardStore = DashboardStore;
}
@@ -41,14 +40,16 @@ public class NetworkCheck : BackgroundService
PingReply reply = p.Send(host, 3000);
if (reply.Status == IPStatus.Success)
{
_NetworkCheckStore.NetworkStatus = "online";
_DashboardStore.NetworkStatus = "online";
_logger.LogInformation("Ping successfull. Watcher is online.");
}
}
catch
{
_NetworkCheckStore.NetworkStatus = "offline";
_DashboardStore.NetworkStatus = "offline";
_logger.LogError("Ping failed. Watcher appears to have no network connection.");
// LogEvent erstellen
}
_logger.LogInformation("Networkcheck finished.");

View File

@@ -1,6 +0,0 @@
namespace Watcher.Services;
public class NetworkCheckStore : INetworkCheckStore
{
public String? NetworkStatus { get; set; }
}

View File

@@ -15,6 +15,7 @@ namespace Watcher.ViewModels
public List<Container> Containers { get; set; } = new();
public String? NetworkStatus { get; set; } = "?";
public String? DatabaseStatus { get; set; } = "?";
}
}

View File

@@ -81,10 +81,31 @@
}
}
@if (!Model.DatabaseStatus.IsNullOrEmpty())
{
@if (Model.DatabaseStatus == "ok")
{
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Datenbank</span>
<span class="badge bg-success">OK</span>
<span class="badge bg-success">healthy</span>
</div>
} else
{
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Datenbank</span>
<span class="badge bg-danger">unhealthy</span>
</div>
}
} else
{
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Datenbank</span>
<span class="badge bg-primary">Missing Data</span>
</div>
}
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-success w-100"></div>
</div>
@@ -113,6 +134,7 @@
</div>
<!-- Services -->
<!-- TODO
<div class="col-12 col-lg-6">
<div class="card shadow rounded-3 p-4 h-100">
<h2 class="h5 fw-semibold mb-3">Services</h2>
@@ -129,18 +151,20 @@
</ul>
</div>
</div>
-->
<!-- Server Liste -->
<!-- TODO
<div class="col-12 col-lg-6">
<div class="card shadow rounded-3 p-4 h-100">
<h2 class="h5 fw-semibold mb-3">Server</h2>
<ul class="list-group list-group-flush">
@foreach (var server in Model.Servers)
@foreach (Server server in Model.Servers)
{
<li class="list-group-item d-flex justify-content-between align-items-center serverlist">
<span>@server.Name</span>
<span class="badge bg-info" )">
CPU: 30.45%
CPU:
</span>
<span class="badge bg-info" )">
RAM: 65.09%
@@ -152,6 +176,7 @@
}
</ul>
</div>
-->
</div>