53 Commits

Author SHA1 Message Date
8383cc88c8 Merge branch 'staging' of https://git.triggermeelmo.com/watcher/Watcher into feature/service-detection 2025-11-05 21:13:15 +01:00
89ffce20da stuff 2025-10-30 12:53:24 +01:00
2e7741f6fe ContainerMetrics angefangen 2025-10-30 11:16:56 +01:00
8e8bfaee45 Merge pull request 'Fixed Json Parser' (#37) from feature/service-detection into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 1m5s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m59s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #37
2025-10-30 08:34:09 +01:00
42a40e8b2e Fixed Json Parser 2025-10-30 08:33:35 +01:00
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
98080df509 added push listening 2025-10-04 13:36:23 +02:00
286f72eac7 moved watcher-server to watcher 2025-10-04 13:32:28 +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
b012693c21 f 2025-10-04 13:29:28 +02:00
e385eb94f4 e
Some checks failed
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) Failing after 15s
Gitea CI/CD / Create Tag (push) Has been skipped
2025-10-04 13:26:08 +02:00
285ff89cb0 d
Some checks failed
Gitea CI/CD / build-and-test (push) Successful in 47s
Gitea CI/CD / Set Tag Name (push) Successful in 4s
Gitea CI/CD / Create Tag (push) Successful in 6s
Gitea CI/CD / docker-build-and-push (push) Failing after 12s
2025-10-04 13:20:58 +02:00
fe45d901e4 Merge pull request 'c' (#27) from bugix/pipeline into staging
Reviewed-on: #27
2025-10-04 13:16:50 +02:00
ba6e201adc c 2025-10-04 13:15:59 +02:00
5867cfc3e1 Merge pull request 'b' (#26) from bugix/pipeline into staging
Reviewed-on: #26
2025-10-04 13:11:47 +02:00
8771e1ee02 b 2025-10-04 13:11:18 +02:00
2be4331a6e Merge pull request 'a' (#25) from bugix/pipeline into staging
Reviewed-on: #25
2025-10-04 13:07:11 +02:00
98754be109 a 2025-10-04 13:06:43 +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
9920c94a8b changed to pull request only 2025-10-04 13:04:30 +02:00
b7bc477d2e .gitea/workflows/build.yaml aktualisiert 2025-10-04 13:02:11 +02:00
ab11665665 dani des is men push vorm pennen gehen
Some checks failed
Gitea CI/CD / Set Tag Name (push) Successful in 6s
Gitea CI/CD / build-and-test (push) Successful in 48s
Gitea CI/CD / Create Tag (push) Has been cancelled
Gitea CI/CD / docker-build-and-push (push) Has been cancelled
2025-10-04 02:06:22 +02:00
37468b6785 added version tag output
All checks were successful
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / build-and-test (push) Successful in 45s
Gitea CI/CD / docker-build-and-push (push) Successful in 5m57s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 01:51:56 +02:00
471767c4ed added farther checking for existing tags 2025-10-04 01:43:57 +02:00
596baba5ef added permissions for packages and moved token to actions checkout
All checks were successful
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / build-and-test (push) Successful in 44s
Gitea CI/CD / docker-build-and-push (push) Successful in 5m39s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 01:41:06 +02:00
12390031f9 added permissions to write
All checks were successful
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / build-and-test (push) Successful in 52s
Gitea CI/CD / docker-build-and-push (push) Successful in 7m29s
Gitea CI/CD / Create Tag (push) Successful in 7s
2025-10-04 01:15:16 +02:00
daed8c1462 .gitea/workflows/build.yaml aktualisiert
Some checks failed
Gitea CI/CD / Set Tag Name (push) Successful in 5s
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 00:52:35 +02:00
7e5e295590 .gitea/workflows/build.yaml aktualisiert
Some checks failed
Gitea CI/CD / Set Tag Name (push) Successful in 6s
Gitea CI/CD / build-and-test (push) Successful in 51s
Gitea CI/CD / Create Tag (push) Has been cancelled
Gitea CI/CD / docker-build-and-push (push) Has been cancelled
2025-10-04 00:34:47 +02:00
cb91ca3159 .gitea/workflows/build.yaml aktualisiert
All checks were successful
Gitea CI/CD / build-and-test (push) Successful in 1m5s
Gitea CI/CD / Set Tag Name (push) Successful in 7s
Gitea CI/CD / docker-build-and-push (push) Successful in 5m56s
Gitea CI/CD / Create Tag (push) Successful in 5s
2025-10-04 00:20:55 +02:00
21 changed files with 1719 additions and 74 deletions

View File

@@ -3,23 +3,22 @@ name: Gitea CI/CD
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ "development", "main", "staging"]
tags: [ "v*.*.*" ]
pull_request:
branches: [ "development", "main", "staging" ] branches: [ "development", "main", "staging" ]
tags: [ "v*.*.*" ]
env: env:
DOTNET_VERSION: '8.0.x' DOTNET_VERSION: '8.0.x'
DOCKER_IMAGE_NAME: watcher-server 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'
TAG: ${{ github.ref == 'refs/heads/main' && 'latest' || github.ref == 'refs/heads/development' && 'development' || github.ref_type == 'tag' && github.ref_name || 'pr' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
build-and-test: dotnet-build-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -44,11 +43,12 @@ jobs:
set-tag: set-tag:
name: Set Tag Name name: Set Tag Name
needs: [dotnet-build-and-test]
#if: ${{ !failure() && !cancelled() && github.event_name != 'pull_request' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
RUNNER_TOOL_CACHE: /toolcache
outputs: outputs:
tag_name: ${{ steps.set_tag.outputs.tag_name }} tag_name: ${{ steps.set_tag.outputs.tag_name }}
should_tag: ${{ steps.set_tag.outputs.should_tag }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -72,21 +72,33 @@ jobs:
major=$((major + 1)) major=$((major + 1))
minor=0 minor=0
patch=0 patch=0
new_tag="v${major}.${minor}.${patch}"
echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT
echo "should_tag=true" >> $GITHUB_OUTPUT
echo "Creating new major version tag: ${new_tag}"
elif [[ "${GITHUB_REF}" == "refs/heads/development" ]]; then elif [[ "${GITHUB_REF}" == "refs/heads/development" ]]; then
minor=$((minor + 1)) minor=$((minor + 1))
patch=0 patch=0
else
patch=$((patch + 1))
fi
new_tag="v${major}.${minor}.${patch}" new_tag="v${major}.${minor}.${patch}"
echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT
echo "should_tag=true" >> $GITHUB_OUTPUT
echo "Creating new minor version tag: ${new_tag}"
elif [[ "${GITHUB_REF}" == "refs/heads/staging" ]]; then
patch=$((patch + 1))
new_tag="v${major}.${minor}.${patch}"
echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT
echo "should_tag=true" >> $GITHUB_OUTPUT
echo "Creating new patch version tag: ${new_tag}"
fi
docker-build-and-push: docker-build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: needs: [dotnet-build-and-test, set-tag]
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache if: |
needs: [build-and-test, set-tag] needs.set-tag.outputs.should_tag == 'true' &&
github.event_name != 'pull_request'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -110,10 +122,11 @@ jobs:
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
env:
RUNNER_TOOL_CACHE: /toolcache
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -128,5 +141,6 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
echo "Creating new tag: ${{ needs.set-tag.outputs.tag_name }}"
git tag ${{ needs.set-tag.outputs.tag_name }} git tag ${{ needs.set-tag.outputs.tag_name }}
git push origin ${{ needs.set-tag.outputs.tag_name }} git push origin ${{ needs.set-tag.outputs.tag_name }}

View File

@@ -3,10 +3,39 @@ import urllib.request
url = "http://localhost:5000/monitoring/service-discovery" url = "http://localhost:5000/monitoring/service-discovery"
payload = { payload = {
"ServerId": 1, "Server_id": 2,
"ContainerId": "aaaaaaaaaaaa", "Containers": [
"Name": "test-Name", {
"Image": "test-Image" "id": "3e74abf5ce30",
"image": "hello-world:latest",
"name": "serene_nightingale"
},
{
"id": "83cd9d461690",
"image": "postgres:latest",
"name": "distracted_feistel"
},
{
"id": "b296c2ed1213",
"image": "postgres:latest",
"name": "mystifying_jackson"
},
{
"id": "69568181d576",
"image": "hello-world:latest",
"name": "romantic_driscoll"
},
{
"id": "67c37a2b1791",
"image": "hello-world:latest",
"name": "asdf"
},
{
"id": "8f39bae1e316",
"image": "hello-world:latest",
"name": "distracted_mirzakhani"
}
]
} }
data = json.dumps(payload).encode("utf-8") data = json.dumps(payload).encode("utf-8")

117
Tests/test.json Normal file
View File

@@ -0,0 +1,117 @@
{
"server_id": 1,
"containers": [
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
},
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
},
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
},
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
},
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
},
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
},
{
"cpu": {
"cpu_load": 0.0
},
"network": {
"net_in": null,
"net_out": null
},
"ram": {
"ram_load": 0.0
},
"container_id": "aaaaaaaa",
"status": {
"status": "exited"
}
}
]
}

View File

@@ -18,7 +18,7 @@ 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();
@@ -29,6 +29,27 @@ public class ContainerController : Controller
}; };
return View(viewModel); return View(viewModel);
} }
public async Task<IActionResult> RestartContainer(string container_id)
{
//Befehl an Agent schicken??
throw new NotImplementedException();
}
public async Task<IActionResult> StopContainer(string container_id)
{
//Befehl an Agent schicken??
throw new NotImplementedException();
}
public async Task<IActionResult> UpdateContainer(string container_id)
{
//Befehl an Agent schicken??
throw new NotImplementedException();
}
} }

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;
@@ -78,12 +82,16 @@ public class MetricDto
public double NET_Out { get; set; } // Bytes/s public double NET_Out { get; set; } // Bytes/s
} }
public class ServiceDto public class DockerServiceDto
{ {
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 JsonElement Containers { get; set; }
public String? Image { get; set; } }
public String? Name { get; set; }
public class DockerServiceMetricDto
{
public required int Server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
public required JsonElement Containers { get; set; }
} }
[ApiController] [ApiController]
@@ -224,7 +232,7 @@ public class MonitoringController : Controller
// Endpoint, an dem Agents Ihre laufenden Services registrieren // Endpoint, an dem Agents Ihre laufenden Services registrieren
[HttpPost("service-discovery")] [HttpPost("service-discovery")]
public async Task<IActionResult> ServiceDetection([FromBody] ServiceDto dto) public async Task<IActionResult> ServiceDetection([FromBody] DockerServiceDto dto)
{ {
// Gültigkeit des Payloads prüfen // Gültigkeit des Payloads prüfen
if (!ModelState.IsValid) if (!ModelState.IsValid)
@@ -234,33 +242,116 @@ 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 List<Container> newContainers =
JsonSerializer.Deserialize<List<Container>>(dto.Containers.GetRawText())
?? new List<Container>();
// Container, die zu ServerId gehören löschen foreach (Container c in newContainers)
{
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 neu in Datenbank einlesen }
// 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 try
{ {
Container container = new Container _context.Containers.Add(c);
{
ServerId = dto.ServerId,
ContainerId = dto.ContainerId,
Image = dto.Name,
Name = dto.Name
};
_logger.LogInformation(container.Name + " added for Host " + container.ServerId);
_context.Containers.Add(container);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInformation(c.Name + " added for Host " + c.ServerId);
} }
catch (SqliteException e) catch (SqliteException e)
{ {
_logger.LogError("ServiceDiscovery failed: " + e.Message); _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();
}
// Endpoint, an den der Agent die Metrics der registrierten Container schickt
public async Task<IActionResult> ServiceMetrics([FromBody] DockerServiceMetricDto 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 der eingegangenen Metric
List<ContainerMetric> metrics = new List<ContainerMetric>();
// Json Parser -> Metric Objekt erstellen
// Metrics in die Datenbank eintragen
try
{
foreach (ContainerMetric cm in metrics)
{
_context.ContainerMetrics.Add(cm);
await _context.SaveChangesAsync();
_logger.LogInformation(cm.ContainerId + " Metric received");
return Ok();
}
}
catch (SqliteException e)
{
_logger.LogError(e.Message);
return StatusCode(500);
} }
return Ok(); return Ok();
@@ -268,7 +359,6 @@ public class MonitoringController : Controller
} }
// 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 +458,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

@@ -0,0 +1,397 @@
// <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("20251030093420_ContainerMetricChange")]
partial class ContainerMetricChange
{
/// <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")
.HasAnnotation("Relational:JsonPropertyName", "id");
b.Property<string>("Image")
.HasColumnType("TEXT")
.HasAnnotation("Relational:JsonPropertyName", "image");
b.Property<int?>("ImageId")
.HasColumnType("INTEGER");
b.Property<bool>("IsRunning")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasAnnotation("Relational:JsonPropertyName", "name");
b.Property<int>("ServerId")
.HasColumnType("INTEGER")
.HasAnnotation("Relational:JsonPropertyName", "Server_id");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("TagId");
b.ToTable("Containers");
});
modelBuilder.Entity("Watcher.Models.ContainerMetric", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "cpu_load");
b.Property<int?>("ContainerId")
.HasColumnType("INTEGER")
.HasAnnotation("Relational:JsonPropertyName", "container_id");
b.Property<double>("NET_in")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "net_in");
b.Property<double>("NET_out")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "net_out");
b.Property<double>("RAM_Load")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "ram_load");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER")
.HasAnnotation("Relational:JsonPropertyName", "server_id");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ContainerMetrics");
});
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,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Watcher.Migrations
{
/// <inheritdoc />
public partial class ContainerMetricChange : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ContainerMetrics",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Timestamp = table.Column<DateTime>(type: "TEXT", nullable: false),
ContainerId = table.Column<int>(type: "INTEGER", nullable: true),
ServerId = table.Column<int>(type: "INTEGER", nullable: true),
CPU_Load = table.Column<double>(type: "REAL", nullable: false),
RAM_Load = table.Column<double>(type: "REAL", nullable: false),
NET_in = table.Column<double>(type: "REAL", nullable: false),
NET_out = table.Column<double>(type: "REAL", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ContainerMetrics", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ContainerMetrics");
}
}
}

View File

@@ -24,10 +24,12 @@ namespace Watcher.Migrations
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("ContainerId") b.Property<string>("ContainerId")
.HasColumnType("TEXT"); .HasColumnType("TEXT")
.HasAnnotation("Relational:JsonPropertyName", "id");
b.Property<string>("Image") b.Property<string>("Image")
.HasColumnType("TEXT"); .HasColumnType("TEXT")
.HasAnnotation("Relational:JsonPropertyName", "image");
b.Property<int?>("ImageId") b.Property<int?>("ImageId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -36,10 +38,12 @@ namespace Watcher.Migrations
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT"); .HasColumnType("TEXT")
.HasAnnotation("Relational:JsonPropertyName", "name");
b.Property<int>("ServerId") b.Property<int>("ServerId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER")
.HasAnnotation("Relational:JsonPropertyName", "Server_id");
b.Property<int?>("TagId") b.Property<int?>("TagId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -53,6 +57,44 @@ namespace Watcher.Migrations
b.ToTable("Containers"); b.ToTable("Containers");
}); });
modelBuilder.Entity("Watcher.Models.ContainerMetric", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "cpu_load");
b.Property<int?>("ContainerId")
.HasColumnType("INTEGER")
.HasAnnotation("Relational:JsonPropertyName", "container_id");
b.Property<double>("NET_in")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "net_in");
b.Property<double>("NET_out")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "net_out");
b.Property<double>("RAM_Load")
.HasColumnType("REAL")
.HasAnnotation("Relational:JsonPropertyName", "ram_load");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER")
.HasAnnotation("Relational:JsonPropertyName", "server_id");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ContainerMetrics");
});
modelBuilder.Entity("Watcher.Models.Image", b => modelBuilder.Entity("Watcher.Models.Image", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

View File

@@ -1,14 +1,23 @@
using System.Text.Json.Serialization;
namespace Watcher.Models; namespace Watcher.Models;
public class Container public class Container
{ {
public int Id { get; set; } public int Id { get; set; }
[JsonPropertyName("Server_id")]
public int ServerId { get; set; } public int ServerId { get; set; }
[JsonPropertyName("id")]
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,33 @@
using System.Text.Json.Serialization;
namespace Watcher.Models;
public class ContainerMetric
{
// Metric Metadata
public int Id { get; set; }
public DateTime Timestamp { get; set; }
// allgemeine Conainer Informationen
[JsonPropertyName("container_id")]
public int? ContainerId { get; set; }
[JsonPropertyName("server_id")]
public int? ServerId { get; set; }
// CPU-Daten
[JsonPropertyName("cpu_load")]
public double CPU_Load { get; set; } = 0.0; // %
// RAM-Daten
[JsonPropertyName("ram_load")]
public double RAM_Load { get; set; } = 0.0; // %
// Network-Daten
[JsonPropertyName("net_in")]
public double NET_in { get; set; } = 0.0; // Bit
[JsonPropertyName("net_out")]
public double NET_out { get; set; } = 0.0; // Bit
}

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,31 @@
<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> <td><a class="ServiceEntry" href="/Server/Details/@container.ServerId">@container.ServerId</a></td>
<td>Docker</td> <td>
<a asp-action="#" asp-route-id="#" class="btn btn-outline-primary">
<i class="bi bi-pencil-square me-1"></i> Neustart
</a>
<a asp-action="#" asp-route-id="#" class="btn btn-outline-primary">
<i class="bi bi-pencil-square me-1"></i> Stop
</a>
<a asp-action="#" asp-route-id="#" class="btn btn-outline-primary">
<i class="bi bi-pencil-square me-1"></i> Update
</a>
</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>