30 Commits

Author SHA1 Message Date
b8b0520eaa Merge pull request 'Merge Errors fixed' (#36) from feature/service-detection into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 50s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 7m46s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #36
2025-10-29 20:58:18 +01:00
df1c4ca4b4 Merge Errors fixed 2025-10-29 20:57:57 +01:00
33acb5ea3a Merge pull request 'feature/service-detection' (#35) from feature/service-detection into staging
Some checks failed
Gitea CI/CD / dotnet-build-and-test (push) Failing after 41s
Gitea CI/CD / Set Tag Name (push) Has been skipped
Gitea CI/CD / docker-build-and-push (push) Has been skipped
Gitea CI/CD / Create Tag (push) Has been skipped
Reviewed-on: #35
2025-10-29 20:55:35 +01:00
a6d992c874 Merge branch 'feature/service-detection' of https://git.triggermeelmo.com/Watcher/watcher into feature/service-detection 2025-10-29 20:54:43 +01:00
8475083897 ServiceMetrics angefangen 2025-10-29 20:52:16 +01:00
f1287314f4 Parse funktioniert mit List<String> als Eingabe 2025-10-29 20:51:04 +01:00
e1684d4484 Frontend Changes + Model angepasst 2025-10-29 12:11:03 +01:00
f113147972 renamed variables 2025-10-29 08:50:16 +01:00
6354075a5c Merge pull request 'feature/service-detection' (#33) from feature/service-detection into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 57s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 7m19s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #33
2025-10-29 07:57:44 +01:00
7c85e338fa Json Parsing 2025-10-29 07:57:13 +01:00
c01401eb07 Logik um ServiceDiscovery Payload zu verarbeiten soweit gemacht, JSON Input wird aber nocht nicht geparsed 2025-10-28 15:25:08 +01:00
7c06f7ee06 Merge pull request 'enhancement/Dokumentation' (#31) from enhancement/Dokumentation into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 54s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m27s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #31
2025-10-27 11:51:36 +01:00
6eed3c764e swagger working /api/v1/swagger 2025-10-21 19:15:16 +02:00
f207440ae6 added swagger for automatic API Documentation 2025-10-10 18:00:41 +02:00
ef187f8750 Basic Structure of new Background Service SystemManagement 2025-10-09 20:27:37 +02:00
570b5abfa0 Merge pull request 'pipeline job umbenannt' (#29) from bugix/pipeline into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 55s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m17s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #29
2025-10-04 19:08:22 +02:00
8f938f999e pipeline job umbenannt 2025-10-04 19:07:49 +02:00
2249d1a776 adde debugging to build.yml
All checks were successful
Gitea CI/CD / build-and-test (push) Successful in 48s
Gitea CI/CD / Set Tag Name (push) Successful in 4s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m12s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 14:43:02 +02:00
37de21f06b testing tagging
All checks were successful
Gitea CI/CD / build-and-test (push) Successful in 46s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m15s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 14:32:04 +02:00
b9d5ade0f1 l
All checks were successful
Gitea CI/CD / build-and-test (push) Successful in 55s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m0s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 14:19:06 +02:00
921b4a9664 added conditions to tag 2025-10-04 14:02:12 +02:00
0e9555e3f7 added set-tag to tag needs
Some checks failed
Gitea CI/CD / Set Tag Name (push) Has been cancelled
Gitea CI/CD / docker-build-and-push (push) Has been cancelled
Gitea CI/CD / Create Tag (push) Has been cancelled
Gitea CI/CD / build-and-test (push) Has been cancelled
2025-10-04 14:00:58 +02:00
19c7aaaca1 Merge branch 'staging' of https://git.triggermeelmo.com/watcher/Watcher into staging
All checks were successful
Gitea CI/CD / build-and-test (push) Successful in 45s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m8s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 13:42:49 +02:00
068c67d0d9 Merge branch 'bugix/pipeline' into staging 2025-10-04 13:40:55 +02:00
4c6635f989 fixed misspelling set_tag to set-tag 2025-10-04 13:40:17 +02:00
d49977815d Merge pull request 'd' (#28) from bugix/pipeline into staging
Some checks failed
Gitea CI/CD / build-and-test (push) Successful in 45s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Failing after 11s
Gitea CI/CD / Create Tag (push) Has been skipped
Reviewed-on: #28
2025-10-04 13:29:55 +02:00
fe45d901e4 Merge pull request 'c' (#27) from bugix/pipeline into staging
Reviewed-on: #27
2025-10-04 13:16:50 +02:00
5867cfc3e1 Merge pull request 'b' (#26) from bugix/pipeline into staging
Reviewed-on: #26
2025-10-04 13:11:47 +02:00
2be4331a6e Merge pull request 'a' (#25) from bugix/pipeline into staging
Reviewed-on: #25
2025-10-04 13:07:11 +02:00
9ee2750534 Merge pull request 'changed to pull request only' (#24) from bugix/pipeline into staging
Reviewed-on: #24
2025-10-04 13:05:47 +02:00
17 changed files with 1041 additions and 54 deletions

View File

@@ -8,7 +8,7 @@ on:
env: env:
DOTNET_VERSION: '8.0.x' DOTNET_VERSION: '8.0.x'
DOCKER_IMAGE_NAME: watcher DOCKER_IMAGE_NAME: watcher-server
REGISTRY_URL: git.triggermeelmo.com REGISTRY_URL: git.triggermeelmo.com
DOCKER_PLATFORMS: 'linux/amd64,linux/arm64' DOCKER_PLATFORMS: 'linux/amd64,linux/arm64'
@@ -17,7 +17,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build-and-test: dotnet-build-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -43,7 +43,7 @@ jobs:
set-tag: set-tag:
name: Set Tag Name name: Set Tag Name
needs: [build-and-test] needs: [dotnet-build-and-test]
#if: ${{ !failure() && !cancelled() && github.event_name != 'pull_request' }} #if: ${{ !failure() && !cancelled() && github.event_name != 'pull_request' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
@@ -95,7 +95,7 @@ jobs:
docker-build-and-push: docker-build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build-and-test, set-tag] needs: [dotnet-build-and-test, set-tag]
if: | if: |
needs.set-tag.outputs.should_tag == 'true' && needs.set-tag.outputs.should_tag == 'true' &&
github.event_name != 'pull_request' github.event_name != 'pull_request'
@@ -117,12 +117,15 @@ jobs:
run: | run: |
docker buildx build \ docker buildx build \
--platform ${{ env.DOCKER_PLATFORMS }} \ --platform ${{ env.DOCKER_PLATFORMS }} \
-t ${{ env.REGISTRY_URL }}/watcher/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.set_tag.outputs.tag_name }} \ -t ${{ env.REGISTRY_URL }}/watcher/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.set-tag.outputs.tag_name }} \
--push . --push .
tag: tag:
name: Create Tag name: Create Tag
needs: [docker-build-and-push] needs: [docker-build-and-push, set-tag]
if: |
needs.set-tag.outputs.should_tag == 'true' &&
github.event_name != 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -3,10 +3,9 @@ import urllib.request
url = "http://localhost:5000/monitoring/service-discovery" url = "http://localhost:5000/monitoring/service-discovery"
payload = { payload = {
"ServerId": 1, "server_id": 7,
"ContainerId": "aaaaaaaaaaaa", "containers": ["{\"id\":\"6621c5b67c25\",\"image\":\"git.triggermeelmo.com/donpat1to/watcher-agent:v0.1.26\",\"name\":\"watcher-agent\"}",
"Name": "test-Name", "{\"id\":\"b8c86fb260bd\",\"image\":\"git.triggermeelmo.com/watcher/watcher-server:v0.1.10\",\"name\":\"watcher\"}"]
"Image": "test-Image"
} }
data = json.dumps(payload).encode("utf-8") data = json.dumps(payload).encode("utf-8")

View File

@@ -18,17 +18,17 @@ public class ContainerController : Controller
} }
public async Task<IActionResult> Overview() public async Task<IActionResult> Overview()
{ {
var containers = await _context.Containers.ToListAsync(); var containers = await _context.Containers.ToListAsync();
var servers = await _context.Servers.ToListAsync(); var servers = await _context.Servers.ToListAsync();
var viewModel = new ContainerOverviewViewModel var viewModel = new ContainerOverviewViewModel
{ {
Servers = servers, Servers = servers,
Containers = containers Containers = containers
}; };
return View(viewModel); return View(viewModel);
} }
} }

View File

@@ -1,8 +1,12 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@@ -80,10 +84,8 @@ public class MetricDto
public class ServiceDto public class ServiceDto
{ {
public int ServerId { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts public required int server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
public String? ContainerId { get; set; } public required List<String> containers { get; set; }
public String? Image { get; set; }
public String? Name { get; set; }
} }
[ApiController] [ApiController]
@@ -234,41 +236,148 @@ public class MonitoringController : Controller
.Select(e => e.ErrorMessage) .Select(e => e.ErrorMessage)
.ToList(); .ToList();
_logger.LogError("Ungültiger ServiceDetection-Payload."); _logger.LogError("Invalid ServiceDetection-Payload.");
return BadRequest(new { error = "Ungültiger Payload", details = errors }); return BadRequest(new { error = "Invalid Payload", details = errors });
} }
// Metrics für Container, die zu ServerId gehören, löschen // Json zu was brauchbarem machen
//string containersJson = await dto.containers.ReadAsStringAsync();
//List<Container> newContainers = JsonSerializer.Deserialize<List<Container>>(containersJson)?? new List<Container>();;
// Container, die zu ServerId gehören löschen List<Container> newContainers = new List<Container>();
// Container neu in Datenbank einlesen foreach (String s in dto.containers)
try
{ {
Container container = new Container // JSON zu einem JsonDocument parsen
using JsonDocument doc = JsonDocument.Parse(s);
JsonElement root = doc.RootElement;
// Einzelne Werte extrahieren
string id = root.GetProperty("id").GetString() ?? "";
string image = root.GetProperty("image").GetString() ?? "";
string name = root.GetProperty("name").GetString() ?? "";
Container newContainer = new Container
{ {
ServerId = dto.ServerId, ServerId = dto.server_id,
ContainerId = dto.ContainerId, ContainerId = id,
Image = dto.Name, Image = image,
Name = dto.Name Name = name,
}; };
_logger.LogInformation(container.Name + " added for Host " + container.ServerId);
_context.Containers.Add(container); newContainers.Add(newContainer);
await _context.SaveChangesAsync();
} }
catch (SqliteException e)
foreach (Container c in newContainers)
{ {
_logger.LogError("ServiceDiscovery failed: " + e.Message); c.ServerId = dto.server_id;
// Debug Logs
// TODO entfernen wenn fertig getestet
Console.WriteLine("---------");
Console.WriteLine("ServerId: " + c.ServerId);
Console.WriteLine("ContainerId: " + c.ContainerId);
Console.WriteLine("Name: " + c.Name);
Console.WriteLine("Image: " + c.Image);
Console.WriteLine("---------");
}
// Container Objekte erstellen
//List<Container> newContainers = ParseServiceDiscoveryInput(dto.Server_id, containers);
// Liste aller Container, die bereits der übergebenen ServerId zugewiesen sind
List<Container> existingContainers = _context.Containers
.Where(c => c.ServerId == dto.server_id)
.ToList();
// Logik, um Container, die mit dem Payload kamen zu verarbeiten
foreach (Container c in newContainers)
{
// Überprüfen, ob ein übergebener Container bereits für den Host registriert ist
if (existingContainers.Contains(c))
{
_logger.LogInformation("Container with id " + c.ContainerId + " already exists.");
}
// Container auf einen Host/Server registrieren
else
{
// Container in Datenbank einlesen
try
{
_context.Containers.Add(c);
await _context.SaveChangesAsync();
_logger.LogInformation(c.Name + " added for Host " + c.ServerId);
}
catch (SqliteException e)
{
_logger.LogError("Error writing new Containers to Database: " + e.Message);
}
}
}
// Logik um abgeschaltene Container aus der Datenbank zu entfernen
foreach (Container c in existingContainers)
{
// Abfrage, ob bereits vorhandener Container im Payload vorhanden war
if (!newContainers.Contains(c))
{
// Container entfernen
_context.Containers.Remove(c);
await _context.SaveChangesAsync();
// Metrics für den Container entfernen
//Todo
_logger.LogInformation("Container " + c.Name + " (" + c.Id + ") on Host-Id " + c.ServerId + " was successfully removed from the database.");
}
} }
return Ok(); return Ok();
} }
// Endpoint, an den der Agent die Metrics der registrierten Container schickt
/*public async Task<IActionResult> ServiceMetrics([FromBody] ServiceMetricDto dto)
{
// Gültigkeit des Payloads prüfen
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogError("Invalid ServiceDetection-Payload.");
return BadRequest(new { error = "Invalid Payload", details = errors });
}
// Liste an Metrics aus der dto erstellen
List<ContainerMetric> metrics = new List<ContainerMetric>();
// Metrics in die Datenbank eintragen
try
{
foreach (ContainerMetric m in metrics)
{
_context.ContainerMetrics.Add(m);
await _context.SaveChangesAsync();
// _logger.LogInformation(m. + " added for Host " + c.ServerId);
return Ok();
}
}
catch (SqliteException e)
{
_logger.LogError(e.Message);
return StatusCode(500);
}
return Ok();
}*/
// Durchschnittliche Werte Berechnen // Durchschnittliche Werte Berechnen
[HttpGet("median")]
public async Task<IActionResult> CalculateMedian(string Metric, int HoursToMonitor, int ServerId) public async Task<IActionResult> CalculateMedian(string Metric, int HoursToMonitor, int ServerId)
{ {
// Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll // Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll
@@ -368,4 +477,13 @@ public class MonitoringController : Controller
return metric_input; return metric_input;
} }
private List<Container> ParseServiceDiscoveryInput(int server_id, List<Container> containers)
{
List<Container> containerList = new List<Container>();
// JSON-Objekt auslesen und Container-Objekte erstellen
return containerList;
}
} }

View File

@@ -26,6 +26,8 @@ public class AppDbContext : DbContext
public DbSet<Tag> Tags { get; set; } public DbSet<Tag> Tags { get; set; }
public DbSet<ContainerMetric> ContainerMetrics { get; set; }
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

View File

@@ -0,0 +1,355 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Watcher.Data;
#nullable disable
namespace Watcher.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20251029105954_container-changed")]
partial class containerchanged
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ContainerId")
.HasColumnType("TEXT");
b.Property<string>("Image")
.HasColumnType("TEXT");
b.Property<int?>("ImageId")
.HasColumnType("INTEGER");
b.Property<bool>("IsRunning")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("ServerId")
.HasColumnType("INTEGER");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("TagId");
b.ToTable("Containers");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Tag")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("ContainerId")
.HasColumnType("INTEGER");
b.Property<string>("Level")
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ContainerId");
b.HasIndex("ServerId");
b.ToTable("LogEvents");
});
modelBuilder.Entity("Watcher.Models.Metric", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Size")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Usage")
.HasColumnType("REAL");
b.Property<double>("GPU_Load")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Size")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Usage")
.HasColumnType("REAL");
b.Property<double>("NET_In")
.HasColumnType("REAL");
b.Property<double>("NET_Out")
.HasColumnType("REAL");
b.Property<double>("RAM_Load")
.HasColumnType("REAL");
b.Property<double>("RAM_Size")
.HasColumnType("REAL");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Metrics");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<int>("CpuCores")
.HasColumnType("INTEGER");
b.Property<string>("CpuType")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<double>("DISK_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("DiskSpace")
.HasColumnType("TEXT");
b.Property<double>("Disk_Usage_Critical")
.HasColumnType("REAL");
b.Property<double>("Disk_Usage_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("GpuType")
.HasColumnType("TEXT");
b.Property<string>("IPAddress")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
b.Property<bool>("IsVerified")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastSeen")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("RAM_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("RAM_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("RamSize")
.HasColumnType("REAL");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("TagId");
b.ToTable("Servers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Tags");
});
modelBuilder.Entity("Watcher.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<string>("IdentityProvider")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("LastLogin")
.HasColumnType("TEXT");
b.Property<string>("OIDC_Id")
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.HasOne("Watcher.Models.Image", null)
.WithMany("Containers")
.HasForeignKey("ImageId");
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Containers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.HasOne("Watcher.Models.Container", "Container")
.WithMany()
.HasForeignKey("ContainerId");
b.HasOne("Watcher.Models.Server", "Server")
.WithMany()
.HasForeignKey("ServerId");
b.Navigation("Container");
b.Navigation("Server");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Servers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Navigation("Containers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Navigation("Containers");
b.Navigation("Servers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Watcher.Migrations
{
/// <inheritdoc />
public partial class containerchanged : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -0,0 +1,355 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Watcher.Data;
#nullable disable
namespace Watcher.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20251029125404_ContainerMetrics-Added")]
partial class ContainerMetricsAdded
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ContainerId")
.HasColumnType("TEXT");
b.Property<string>("Image")
.HasColumnType("TEXT");
b.Property<int?>("ImageId")
.HasColumnType("INTEGER");
b.Property<bool>("IsRunning")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("ServerId")
.HasColumnType("INTEGER");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("TagId");
b.ToTable("Containers");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Tag")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("ContainerId")
.HasColumnType("INTEGER");
b.Property<string>("Level")
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ContainerId");
b.HasIndex("ServerId");
b.ToTable("LogEvents");
});
modelBuilder.Entity("Watcher.Models.Metric", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Size")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Usage")
.HasColumnType("REAL");
b.Property<double>("GPU_Load")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Size")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Usage")
.HasColumnType("REAL");
b.Property<double>("NET_In")
.HasColumnType("REAL");
b.Property<double>("NET_Out")
.HasColumnType("REAL");
b.Property<double>("RAM_Load")
.HasColumnType("REAL");
b.Property<double>("RAM_Size")
.HasColumnType("REAL");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Metrics");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<int>("CpuCores")
.HasColumnType("INTEGER");
b.Property<string>("CpuType")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<double>("DISK_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("DiskSpace")
.HasColumnType("TEXT");
b.Property<double>("Disk_Usage_Critical")
.HasColumnType("REAL");
b.Property<double>("Disk_Usage_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("GpuType")
.HasColumnType("TEXT");
b.Property<string>("IPAddress")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
b.Property<bool>("IsVerified")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastSeen")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("RAM_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("RAM_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("RamSize")
.HasColumnType("REAL");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("TagId");
b.ToTable("Servers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Tags");
});
modelBuilder.Entity("Watcher.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<string>("IdentityProvider")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("LastLogin")
.HasColumnType("TEXT");
b.Property<string>("OIDC_Id")
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.HasOne("Watcher.Models.Image", null)
.WithMany("Containers")
.HasForeignKey("ImageId");
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Containers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.HasOne("Watcher.Models.Container", "Container")
.WithMany()
.HasForeignKey("ContainerId");
b.HasOne("Watcher.Models.Server", "Server")
.WithMany()
.HasForeignKey("ServerId");
b.Navigation("Container");
b.Navigation("Server");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Servers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Navigation("Containers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Navigation("Containers");
b.Navigation("Servers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Watcher.Migrations
{
/// <inheritdoc />
public partial class ContainerMetricsAdded : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -1,14 +1,23 @@
using System.Text.Json.Serialization;
namespace Watcher.Models; namespace Watcher.Models;
public class Container public class Container
{ {
[JsonPropertyName("id")]
public int Id { get; set; } public int Id { get; set; }
public int ServerId { get; set; } public int ServerId { get; set; }
public String? ContainerId { get; set; } public String? ContainerId { get; set; }
[JsonPropertyName("image")]
public String? Image { get; set; } public String? Image { get; set; }
[JsonPropertyName("name")]
public String? Name { get; set; } public String? Name { get; set; }
// keine Variable, die vom Agent übergeben wird. Ein container ist immer Running, die Variable dient nur für die Übersicht
// auf dem Dashboard.
public Boolean IsRunning { get; set; } = true; public Boolean IsRunning { get; set; } = true;
} }

View File

@@ -0,0 +1,24 @@
namespace Watcher.Models;
public class ContainerMetric
{
// Metric Metadata
public int Id { get; set; }
public DateTime Timestamp { get; set; }
// Zuordnung zu einem Container -- Foreign Key
public int? ContainerId { get; set; }
// CPU-Daten
public double CPU_Load { get; set; } = 0.0; // %
public double CPU_Temp { get; set; } = 0.0; // deg C
// RAM-Daten
public double RAM_Size { get; set; } = 0.0; // GB
public double RAM_Load { get; set; } = 0.0; // %
}

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Serilog; using Serilog;
@@ -36,11 +37,17 @@ builder.Services.AddHttpContextAccessor();
// Storage Singleton // Storage Singleton
builder.Services.AddSingleton<IDashboardStore, DashboardStore>(); builder.Services.AddSingleton<IDashboardStore, DashboardStore>();
builder.Services.AddSingleton<ISystemStore, SystemStore>();
// Background Services // Background Services
builder.Services.AddHostedService<NetworkCheck>(); builder.Services.AddHostedService<NetworkCheck>();
builder.Services.AddHostedService<DatabaseCheck>(); builder.Services.AddHostedService<DatabaseCheck>();
// Swagger API-Dokumentation
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Watcher-Server API", Version = "v1" });
});
// ---------- Konfiguration ---------- // ---------- Konfiguration ----------
DotNetEnv.Env.Load(); DotNetEnv.Env.Load();
@@ -204,22 +211,30 @@ using (var scope = app.Services.CreateScope())
if (!app.Environment.IsDevelopment()) if (!app.Environment.IsDevelopment())
{ {
app.UseExceptionHandler("/Home/Error"); app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
// 🔹 Swagger aktivieren
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Watcher-Server API v1");
options.RoutePrefix = "api/v1/swagger";
});
// 🔹 Authentifizierung & Autorisierung
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseStaticFiles(); // 🔹 MVC-Routing
app.MapControllerRoute( app.MapControllerRoute(
name: "default", name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}" pattern: "{controller=Home}/{action=Index}/{id?}"
); );
app.Run(); app.Run();

View File

@@ -0,0 +1,9 @@
namespace Watcher.Services;
public interface ISystemStore
{
Boolean NewVersionAvailable { get; set; }
Double DatabaseSize { get; set; }
}

View File

@@ -0,0 +1,42 @@
using System.Net.Http;
namespace Watcher.Services;
public class SystemManagement : BackgroundService
{
private readonly ILogger<NetworkCheck> _logger;
private ISystemStore _SystemStore;
public SystemManagement(ILogger<NetworkCheck> logger, ISystemStore SystemStore)
{
_logger = logger;
_SystemStore = SystemStore;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Todo: Umstellen auf einmal alle 24h
var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// Hintergrundprozess abwarten
await checkForNewDockerImageVersion();
// 5 Sekdunden Offset
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
public Task checkForNewDockerImageVersion()
{
return Task.CompletedTask;
}
public Task createDailySqliteBackup()
{
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,9 @@
namespace Watcher.Services;
public class SystemStore: ISystemStore
{
public Boolean NewVersionAvailable { get; set; }
public Double DatabaseSize { get; set; }
}

View File

@@ -12,20 +12,22 @@
<div > <div >
<table class="ServiceList"> <table class="ServiceList">
<tr> <tr>
<th>Service</th> <th>Container-ID</th>
<th>HostServer</th> <th>Name</th>
<th>Status</th> <th>Image</th>
<th>Deployment</th> <th>Host-ID</th>
<th></th> <th>Aktionen</th>
</tr> </tr>
@foreach (Container container in Model.Containers) @foreach (Container container in Model.Containers)
{ {
<tr class="ServiceRow"> <tr class="ServiceRow">
<td>@container.ContainerId</td>
<td>@container.Name</td> <td>@container.Name</td>
<td><a class="ServiceEntry" href="/Server/Details/${containerId}">@container.ServerId</a></td> <td>@container.Image</td>
<td>nicht automatisiert</td> <!-- TODO: Link Hardcoded -->
<td>Docker</td> <td><a class="ServiceEntry" href="/Server/Details/2">@container.ServerId</a></td>
<td>nicht verfügbar</td>
</tr> </tr>
} }
</table> </table>

View File

@@ -23,6 +23,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.6" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>