26 Commits
v0.1.0 ... main

Author SHA1 Message Date
52927bf64d .gitea/workflows/release.yaml aktualisiert
All checks were successful
Release Build and Release / build-and-test (push) Successful in 54s
Release Build and Release / docker-build-and-push (push) Successful in 5m59s
2025-10-01 21:25:27 +02:00
7373ea16e0 .gitea/workflows/release.yaml aktualisiert
All checks were successful
Release Build and Release / build-and-test (push) Successful in 59s
Release Build and Release / docker-build-and-push (push) Successful in 6m22s
2025-10-01 21:16:31 +02:00
9990b35787 Merge pull request 'v0.1.1 Release' (#15) from development into main
Some checks failed
Release Build and Release / build-and-test (push) Successful in 1m14s
Release Build and Release / docker-build-and-push (push) Failing after 11s
Reviewed-on: #15
2025-10-01 21:14:21 +02:00
df7674f063 Merge pull request 'Metrics Fixed' (#14) from bug/sanitize-metrics into development
All checks were successful
Development Build / build-and-test (push) Successful in 1m3s
Development Build / docker-build-and-push (push) Successful in 6m41s
Reviewed-on: #14
2025-10-01 18:20:58 +02:00
9d0a2e40be Metrics Fixed 2025-10-01 18:20:21 +02:00
c8dc8adb0d Merge pull request 'Fixed RAM_LOAD sanitization' (#13) from bug/sanitize-metrics into development
All checks were successful
Development Build / build-and-test (push) Successful in 58s
Development Build / docker-build-and-push (push) Successful in 6m10s
Reviewed-on: #13
2025-10-01 13:17:25 +02:00
0aacf369d7 Fixed RAM_LOAD sanitization 2025-10-01 13:16:43 +02:00
2d8bf648d9 Merge pull request 'bug/sanitize-metrics' (#12) from bug/sanitize-metrics into development
All checks were successful
Development Build / build-and-test (push) Successful in 1m4s
Development Build / docker-build-and-push (push) Successful in 6m29s
Reviewed-on: #12
2025-10-01 12:58:15 +02:00
85c5a80360 sanitizeDegreeInputs 2025-10-01 12:57:27 +02:00
36e16fbcf9 sanitizemetrics eingeführt 2025-10-01 12:52:45 +02:00
5e2f9e4c3c sanitizeMetrics Funktion erstellt 2025-09-30 12:21:25 +02:00
32a6c0d108 Merge pull request 'closes feature/ui-rework' (#10) from feature/ui-rework into development
All checks were successful
Development Build / build-and-test (push) Successful in 1m20s
Development Build / docker-build-and-push (push) Successful in 6m57s
Reviewed-on: #10
2025-09-30 09:08:56 +02:00
d1f348a9fa .gitea/workflows/development-build.yaml aktualisiert
All checks were successful
Development Build / build-and-test (push) Successful in 48s
Development Build / docker-build-and-push (push) Successful in 5m58s
2025-09-25 17:02:19 +02:00
cff9a6699c .gitea/workflows/development-build.yaml aktualisiert
Some checks failed
Development Build / build-and-test (push) Successful in 49s
Development Build / docker-build-and-push (push) Failing after 10s
2025-09-25 16:57:13 +02:00
0bb0c09ce3 .gitea/workflows/development-build.yaml aktualisiert
Some checks failed
Development Build / build-and-test (push) Successful in 57s
Development Build / docker-build-and-push (push) Has been cancelled
2025-09-25 16:55:28 +02:00
5c9c9cd165 .gitea/workflows/development-build.yaml aktualisiert
Some checks failed
Development Build / build-and-test (push) Successful in 1m17s
Development Build / docker-build-and-push (push) Failing after 48s
2025-09-25 16:46:33 +02:00
b481cd764e .gitea/workflows/development-build.yaml aktualisiert
Some checks failed
Development Build / build-and-test (push) Successful in 1m21s
Development Build / docker-build-and-push (push) Has been cancelled
2025-09-25 16:42:05 +02:00
f4fa055edf Logo eingefügt 2025-09-12 15:15:51 +02:00
7def038cc9 stuff 2025-09-12 11:50:04 +02:00
6e6d17b134 Dashboard, Services, Server, Settings, Userinfo Page auf Darkmode umgebaut 2025-09-08 15:06:39 +02:00
ab6f99eb6b UI Overhaul 2025-09-07 01:08:06 +02:00
62f384cc7b Merge branch 'development' of https://git.triggermeelmo.com/watcher/Watcher into feature/ui-rework 2025-09-06 22:16:52 +02:00
c8480bb681 docker-compose.yaml aktualisiert 2025-09-02 11:33:15 +02:00
1c0fab71bb new Dashboard 2025-08-26 18:54:17 +02:00
aa35e83f6b Merge branch 'main' of https://git.triggermeelmo.com/watcher/Watcher into development 2025-08-23 00:07:04 +02:00
cd6d2a1825 Merge branch 'main' of https://git.triggermeelmo.com/watcher/Watcher into development 2025-08-22 23:01:00 +02:00
44 changed files with 773 additions and 463 deletions

View File

@@ -1,4 +1,4 @@
name: Development Build and Release name: Development Build
on: on:
push: push:
@@ -10,6 +10,7 @@ env:
DOCKER_IMAGE_NAME: 'watcher-server' DOCKER_IMAGE_NAME: 'watcher-server'
REGISTRY_URL: 'git.triggermeelmo.com/watcher' REGISTRY_URL: 'git.triggermeelmo.com/watcher'
DOCKER_PLATFORMS: 'linux/amd64,linux/arm64' DOCKER_PLATFORMS: 'linux/amd64,linux/arm64'
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
jobs: jobs:
build-and-test: build-and-test:
@@ -47,11 +48,11 @@ jobs:
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to Gitea Container Registry - name: Login to Gitea Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: git.triggermeelmo.com registry: ${{ env.REGISTRY_URL}}
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.AUTOMATION_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.AUTOMATION_PASSWORD }}
- name: Build and Push Multi-Arch Docker Image - name: Build and Push Multi-Arch Docker Image
run: | run: |

View File

@@ -50,13 +50,13 @@ jobs:
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: git.triggermeelmo.com registry: git.triggermeelmo.com
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.AUTOMATION_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.AUTOMATION_PASSWORD }}
- name: Build and Push Multi-Arch Docker Image - name: Build and Push Multi-Arch Docker Image
run: | run: |
docker buildx build \ docker buildx build \
--platform ${{ env.DOCKER_PLATFORMS }} \ --platform ${{ env.DOCKER_PLATFORMS }} \
-t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:v0.1.0 \ -t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:v0.1.1 \
-t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} \ -t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} \
--push . --push .

View File

@@ -1,23 +0,0 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.13"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
# install:
# - requirements: docs/requirements.txt

View File

@@ -0,0 +1,32 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Watcher.Data;
[Route("[controller]/v1")]
public class ApiController : Controller
{
private readonly AppDbContext _context;
private readonly ILogger<ApiController> _logger;
public ApiController(AppDbContext context, ILogger<ApiController> logger)
{
_context = context;
_logger = logger;
}
[HttpGet("reference")]
public IActionResult ApiReference()
{
return View();
}
[HttpGet("servers")]
public async Task<IActionResult> GetAllServers()
{
var Servers = await _context.Servers.OrderBy(s => s.Id).ToListAsync();
return Ok();
}
}

View File

@@ -116,7 +116,7 @@ namespace Watcher.Controllers
TempData["DumpMessage"] = "SQLite-Dump erfolgreich erstellt."; TempData["DumpMessage"] = "SQLite-Dump erfolgreich erstellt.";
_logger.LogInformation("SQLite-Dump erfolgreich erstellt."); _logger.LogInformation("SQLite-Dump erfolgreich erstellt.");
return RedirectToAction("UserSettings", "User"); return RedirectToAction("Settings", "System");
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
// Local Namespaces // Local Namespaces
using Watcher.Data; using Watcher.Data;
using Watcher.Models;
using Watcher.ViewModels; using Watcher.ViewModels;
namespace Watcher.Controllers namespace Watcher.Controllers
@@ -41,7 +42,17 @@ namespace Watcher.Controllers
OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline), OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning), RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning),
FailedContainers = await _context.Containers.CountAsync(c => !c.IsRunning), FailedContainers = await _context.Containers.CountAsync(c => !c.IsRunning),
LastLogin = user?.LastLogin ?? DateTime.MinValue LastLogin = user?.LastLogin ?? DateTime.MinValue,
Servers = await _context.Servers
.OrderBy(s => s.Name)
.ToListAsync(),
RecentEvents = await _context.LogEvents
.OrderByDescending(e => e.Timestamp)
.Take(20)
.ToListAsync(),
Containers = await _context.Containers
.OrderBy(s => s.Name)
.ToListAsync()
}; };
return View(viewModel); return View(viewModel);
@@ -49,7 +60,7 @@ namespace Watcher.Controllers
// Funktion für /Views/Home/Index.cshtml um das DashboardStats-Partial neu zu laden. // Funktion für /Views/Home/Index.cshtml um das DashboardStats-Partial neu zu laden.
// Die Funktion wird nicht direkt aufgerufen, sondern nur der /Home/DashboardStats Endpoint angefragt. // Die Funktion wird nicht direkt aufgerufen, sondern nur der /Home/DashboardStats Endpoint angefragt.
public IActionResult DashboardStats() public async Task<IActionResult> DashboardStats()
{ {
var servers = _context.Servers.ToList(); var servers = _context.Servers.ToList();
var containers = _context.Containers.ToList(); var containers = _context.Containers.ToList();
@@ -58,14 +69,20 @@ namespace Watcher.Controllers
var model = new DashboardViewModel var model = new DashboardViewModel
{ {
ActiveServers = servers.Count(s => (now - s.LastSeen).TotalSeconds <= 120), ActiveServers = await _context.Servers.CountAsync(s => s.IsOnline),
OfflineServers = servers.Count(s => (now - s.LastSeen).TotalSeconds > 120), OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning),
//TODO: anwendbar, wenn Container implementiert wurden. FailedContainers = await _context.Containers.CountAsync(c => !c.IsRunning),
//RunningContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds <= 120), Servers = await _context.Servers
//FailedContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds > 120), .OrderBy(s => s.Name)
.ToListAsync(),
LastLogin = DateTime.Now RecentEvents = await _context.LogEvents
.OrderByDescending(e => e.Timestamp)
.Take(20)
.ToListAsync(),
Containers = await _context.Containers
.OrderBy(s => s.Name)
.ToListAsync()
}; };
return PartialView("_DashboardStats", model); return PartialView("_DashboardStats", model);

View File

@@ -55,26 +55,26 @@ public class MetricDto
public double GPU_Temp { get; set; } // deg C public double GPU_Temp { get; set; } // deg C
public double GPU_Vram_Size { get; set; } // GB public double GPU_Vram_Size { get; set; } // Bytes
public double GPU_Vram_Usage { get; set; } // % public double GPU_Vram_Load { get; set; } // %
// RAM // RAM
public double RAM_Size { get; set; } // GB public double RAM_Size { get; set; } // Bytes
public double RAM_Load { get; set; } // % public double RAM_Load { get; set; } // %
// Disks // Disks
public double DISK_Size { get; set; } // GB public double DISK_Size { get; set; } // Bytes
public double DISK_Usage { get; set; } // % public double DISK_Usage { get; set; } // Bytes
public double DISK_Temp { get; set; } // deg C public double DISK_Temp { get; set; } // deg C (if available)
// Network // Network
public double NET_In { get; set; } // Bit public double NET_In { get; set; } // Bytes/s
public double NET_Out { get; set; } // Bit public double NET_Out { get; set; } // Bytes/s
} }
@@ -173,26 +173,28 @@ public class MonitoringController : Controller
if (server != null) if (server != null)
{ {
// neues Metric-Objekt erstellen
var NewMetric = new Metric var NewMetric = new Metric
{ {
Timestamp = DateTime.UtcNow, Timestamp = DateTime.UtcNow,
ServerId = dto.ServerId, ServerId = dto.ServerId,
CPU_Load = dto.CPU_Load, CPU_Load = sanitizeInput(dto.CPU_Load),
CPU_Temp = dto.CPU_Temp, CPU_Temp = sanitizeInput(dto.CPU_Temp),
GPU_Load = dto.GPU_Load, GPU_Load = sanitizeInput(dto.GPU_Load),
GPU_Temp = dto.GPU_Temp, GPU_Temp = sanitizeInput(dto.GPU_Temp),
GPU_Vram_Size = dto.GPU_Vram_Size, GPU_Vram_Size = calculateGigabyte(dto.GPU_Vram_Size),
GPU_Vram_Usage = dto.GPU_Vram_Usage, GPU_Vram_Usage = sanitizeInput(dto.GPU_Vram_Load),
RAM_Load = dto.RAM_Load, RAM_Load = sanitizeInput(dto.RAM_Load),
RAM_Size = dto.RAM_Size, RAM_Size = calculateGigabyte(dto.RAM_Size),
DISK_Size = dto.RAM_Size, DISK_Size = calculateGigabyte(dto.DISK_Size),
DISK_Usage = dto.DISK_Usage, DISK_Usage = calculateGigabyte(dto.DISK_Usage),
DISK_Temp = dto.DISK_Temp, DISK_Temp = sanitizeInput(dto.DISK_Temp),
NET_In = dto.NET_In, NET_In = calculateMegabit(dto.NET_In),
NET_Out = dto.NET_Out NET_Out = calculateMegabit(dto.NET_Out)
}; };
try try
{ {
// Metric Objekt in Datenbank einfügen
_context.Metrics.Add(NewMetric); _context.Metrics.Add(NewMetric);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@@ -281,4 +283,40 @@ public class MonitoringController : Controller
return Ok(data); return Ok(data);
} }
// Metric Input Byte zu Gigabyte umwandeln
public static double calculateGigabyte(double metric_input)
{
// *10^-9 um auf Gigabyte zu kommen
double calculatedValue = metric_input * Math.Pow(10, -9);
// Auf 2 Nachkommastellen runden
double calculatedValue_s = sanitizeInput(calculatedValue);
return calculatedValue_s;
}
// Metric Input Byte/s zu Megabit/s umrechnen
//TODO
public static double calculateMegabit(double metric_input)
{
// *10^-9 um auf Gigabyte zu kommen
double calculatedValue = metric_input * Math.Pow(10, -9);
// Auf 2 Nachkommastellen runden
double calculatedValue_s = sanitizeInput(calculatedValue);
return calculatedValue_s;
}
// Degree Input auf zwei Nachkommastellen runden
public static double sanitizeInput(double metric_input)
{
Math.Round(metric_input, 2);
return metric_input;
}
} }

View File

@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Watcher.Data;
using Watcher.ViewModels;
namespace Watcher.Controllers;
[Authorize]
[Route("[controller]")]
public class SystemController : Controller
{
private readonly AppDbContext _context;
private readonly ILogger<SystemController> _logger;
public SystemController(AppDbContext context, ILogger<SystemController> logger)
{
_context = context;
_logger = logger;
}
// Edit-Form anzeigen
[HttpGet("Settings")]
//public async Task<IActionResult> Settings()
public IActionResult Settings()
{
ViewBag.DbProvider = "Microsoft.EntityFrameworkCore.Sqlite";
ViewBag.mail = "test@mail.com";
ViewBag.IdentityProvider = "Local";
ViewBag.ServerVersion = "v0.1.0";
return View();
}
// HttpPost
// public IActionResult UpdateNotifications(){}
}

View File

@@ -55,6 +55,7 @@ public class UserController : Controller
var user = _context.Users.FirstOrDefault(u => u.Username == username); var user = _context.Users.FirstOrDefault(u => u.Username == username);
if (user == null) return NotFound(); if (user == null) return NotFound();
var model = new EditUserViewModel var model = new EditUserViewModel
{ {
Username = user.Username Username = user.Username
@@ -90,31 +91,6 @@ public class UserController : Controller
return RedirectToAction("Index", "Home"); return RedirectToAction("Index", "Home");
} }
// Edit-Form anzeigen
[Authorize]
[HttpGet]
public IActionResult UserSettings()
{
var username = User.Identity?.Name;
Console.WriteLine("gefundener User: " + username);
var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToList();
var user = _context.Users.FirstOrDefault(u => u.Username == username);
if (user == null) return NotFound();
var DbProvider = _context.Database.ProviderName;
var mail = user.Email;
ViewBag.Name = username;
ViewBag.mail = mail;
ViewBag.Claims = claims;
ViewBag.IdentityProvider = user.IdentityProvider;
ViewBag.DbProvider = DbProvider;
return View();
}
// Edit speichern // Edit speichern
[Authorize] [Authorize]
[HttpPost] [HttpPost]

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Watcher.Models
{
public class HealthStatus
{
public DateTime Timestamp { get; set; }
public bool NetworkOk { get; set; }
public bool DatabaseOk { get; set; }
public List<string> Issues { get; set; } = new List<string>();
// Optional weitere Checks
public bool ApiOk { get; set; }
public bool DiskOk { get; set; }
}
}

View File

@@ -1,14 +1,12 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Sqlite;
using Microsoft.IdentityModel.Tokens;
using Serilog; using Serilog;
using Watcher.Data; using Watcher.Data;
using Watcher.Models; using Watcher.Models;
//using Watcher.Services;
//using Watcher.Workers;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -32,7 +30,6 @@ builder.Host.UseSerilog();
// Add services to the container. // Add services to the container.
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
// HttpContentAccessor // HttpContentAccessor
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
@@ -112,7 +109,11 @@ builder.Services.AddAuthentication()
var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>(); var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
var principal = ctx.Principal; var principal = ctx.Principal;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
var pocketId = principal.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value; var pocketId = principal.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
#pragma warning restore CS8602 // Dereference of a possibly null reference.
var preferredUsername = principal.FindFirst("preferred_username")?.Value; var preferredUsername = principal.FindFirst("preferred_username")?.Value;
var email = principal.FindFirst("email")?.Value; var email = principal.FindFirst("email")?.Value;

View File

@@ -1,3 +1,5 @@
using Watcher.Models;
namespace Watcher.ViewModels namespace Watcher.ViewModels
{ {
public class DashboardViewModel public class DashboardViewModel
@@ -7,5 +9,10 @@ namespace Watcher.ViewModels
public int RunningContainers { get; set; } public int RunningContainers { get; set; }
public int FailedContainers { get; set; } public int FailedContainers { get; set; }
public DateTime LastLogin { get; set; } public DateTime LastLogin { get; set; }
public List<Server> Servers { get; set; } = new();
public List<LogEvent> RecentEvents { get; set; } = new();
public List<Container> Containers { get; set; } = new();
} }
} }

View File

@@ -0,0 +1 @@
<p>i</p>

View File

@@ -5,55 +5,10 @@
var oidc = ViewBag.oidc; var oidc = ViewBag.oidc;
} }
<style> <head>
body { <link rel="stylesheet" href="~/css/main.css" />
background-color: #0d1b2a; <link rel="stylesheet" href="~/css/Login.css" />
} </head>
.login-card {
background-color: #1b263b;
color: #ffffff;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
max-width: 400px;
margin: auto;
}
.form-control {
background-color: #415a77;
border: none;
color: white;
}
.form-control::placeholder {
color: #c0c0c0;
}
.btn-primary {
background-color: #0d6efd;
border: none;
}
.btn-pocketid {
background-color: #14a44d;
color: white;
border: none;
}
.btn-pocketid:hover {
background-color: #0f8c3c;
}
label {
margin-top: 1rem;
}
.form-error {
color: #ff6b6b;
font-size: 0.875rem;
}
</style>
<div class="login-card"> <div class="login-card">
<h2 class="text-center mb-4">Anmelden</h2> <h2 class="text-center mb-4">Anmelden</h2>

View File

@@ -3,32 +3,37 @@
ViewData["Title"] = "Containerübersicht"; ViewData["Title"] = "Containerübersicht";
} }
<head>
<link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="~/css/services-overview.css" />
</head>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach (var server in Model.Servers) <div >
{ <!-- TODO: Später auf > 0 ändern! -->
<div class="bg-white shadow-md rounded-xl p-5 border border-gray-200 hover:shadow-lg transition duration-200"> <table class="ServiceList">
<h2 class="text-xl font-semibold mb-1">@server.Name</h2> <!-- foreach (var container in Model.Containers)-->
</div> <tr>
<th>Service</th>
<th>HostServer</th>
<th>Status</th>
<th>SSL/TLS</th>
<th>Proxy</th>
<th>Deployment</th>
<div class="bg-white"> </tr>
@if (Model.Containers.Count > 0) @for (int i = 0; i < 3; i++)
{ {
<table> <tr class="ServiceRow">
@foreach (var container in Model.Containers) <td>test</td>
{ <td><a class="ServiceEntry" href="/Server/Details/7">test</a></td>
<tr>test</tr> <td>Online</td>
if (container.HostServer.Equals(server.Name)) <td>true</td>
{ <td>true</td>
<td>Docker</td>
</tr>
}
</table>
</div>
}
}
</table>
} else
{
<p> keine Container gefunden </p>
}
</div>
}
</div> </div>

View File

@@ -3,6 +3,11 @@
ViewData["Title"] = "Datenbank-Dumps"; ViewData["Title"] = "Datenbank-Dumps";
} }
<head>
<link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="~/css/databases.css" />
</head>
<h2 class="mb-4 text-xl font-bold"><i class="bi bi-hdd me-1"></i>Datenbank-Dumps</h2> <h2 class="mb-4 text-xl font-bold"><i class="bi bi-hdd me-1"></i>Datenbank-Dumps</h2>
@if (TempData["Success"] != null) @if (TempData["Success"] != null)
@@ -14,7 +19,7 @@
<div class="alert alert-danger">@TempData["Error"]</div> <div class="alert alert-danger">@TempData["Error"]</div>
} }
<table class="table table-striped"> <table class="dumptable">
<thead> <thead>
<tr> <tr>
<th>Dateiname</th> <th>Dateiname</th>

View File

@@ -3,6 +3,9 @@
ViewData["Title"] = "Dashboard"; ViewData["Title"] = "Dashboard";
} }
<head>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<h1 class="mb-4"> <h1 class="mb-4">
<i class="bi bi-speedometer2 me-2"></i>Dashboard <i class="bi bi-speedometer2 me-2"></i>Dashboard
</h1> </h1>
@@ -11,25 +14,6 @@
@await Html.PartialAsync("_DashboardStats", Model) @await Html.PartialAsync("_DashboardStats", Model)
</div> </div>
<div class="row g-4 mt-4">
<div class="col-12">
<div class="card p-3">
<h2 class="h5">
<i class="bi bi-person-circle me-2"></i>Systeminfo
</h2>
<p>
<i class="bi bi-person-badge me-1"></i>
Benutzer: <strong>@User.FindFirst("preferred_username")?.Value</strong>
</p>
<p>
<i class="bi bi-clock me-1"></i>
Letzter Login: <strong>@Model.LastLogin.ToString("g")</strong>
</p>
</div>
</div>
</div>
@section Scripts { @section Scripts {
<script> <script>
async function loadDashboardStats() { async function loadDashboardStats() {

View File

@@ -1,25 +1,143 @@
@model Watcher.ViewModels.DashboardViewModel @model Watcher.ViewModels.DashboardViewModel
<div class="row g-4"> @{
<div class="col-12 col-md-6"> ViewData["Title"] = "Dashboard";
<div class="bg-white shadow rounded-3 p-4 h-100"> }
<h2 class="h5 fw-semibold mb-2">Server</h2>
<p>🟢 Online: <strong>@Model.ActiveServers</strong></p> <head>
<p>🔴 Offline: <strong>@Model.OfflineServers</strong></p> <link rel="stylesheet" href="~/css/main.css" />
<a href="/Server/Overview" class="text-primary text-decoration-none mt-2 d-inline-block"> <link rel="stylesheet" href="~/css/dashboardstats.css" />
→ Zu den Servern </head>
</a>
<body>
<div class="row g-4 mb-4">
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-hdd-network text-success fs-2 mb-2"></i>
<h6 class="text-text">Server Online</h6>
<h3 class="fw-bold">@Model.ActiveServers</h3>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-hdd-network-fill text-danger fs-2 mb-2"></i>
<h6 class="text-text">Server Offline</h6>
<h3 class="fw-bold">@Model.OfflineServers</h3>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-box-seam text-primary fs-2 mb-2"></i>
<h6 class="text-text">Services Running</h6>
<h3 class="fw-bold">@Model.RunningContainers</h3>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-exclamation-triangle text-warning fs-2 mb-2"></i>
<h6 class="text-text">Service Warnungen</h6>
<h3 class="fw-bold">@Model.FailedContainers</h3>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-12 col-md-6"> <!-- System Overview & Recent Events -->
<div class="bg-white shadow rounded-3 p-4 h-100"> <div class="row g-4">
<h2 class="h5 fw-semibold mb-2">Container</h2> <!-- System Health -->
<p>🟢 Laufend: <strong>@Model.RunningContainers</strong></p> <div class="col-12 col-lg-6">
<p>🔴 Fehlerhaft: <strong>@Model.FailedContainers</strong></p> <div class="card shadow-sm border-0 rounded-3 h-100">
<a href="/Container/Overview" class="text-primary text-decoration-none mt-2 d-inline-block"> <div class="card-body">
→ Zu den Containern <h5 class="fw-bold mb-3"><i class="bi bi-heart-pulse me-2 text-danger"></i>Systemstatus</h5>
</a>
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Netzwerk</span>
<span class="badge bg-success">OK</span>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-success w-100"></div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Datenbank</span>
<span class="badge bg-success">OK</span>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-success w-100"></div>
</div>
</div>
</div>
</div> </div>
</div>
</div> <!-- Recent Events -->
<div class="col-12 col-lg-6">
<div class="card shadow-sm border-0 rounded-3 h-100">
<div class="card-body">
<h5 class="fw-bold mb-3"><i class="bi bi-activity me-2 text-primary"></i>Letzte Ereignisse</h5>
<ul class="list-group list-group-flush small">
@foreach (var log in Model.RecentEvents.Take(1))
{
<li class="list-group-item d-flex align-items-center">
<i class="bi bi-dot fs-4 text-muted me-1"></i>
<span class="text-muted me-2">@log.Timestamp.ToString("HH:mm:ss")</span>
<span>@log</span>
</li>
}
</ul>
</div>
</div>
</div>
<!-- Services -->
<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>
<ul class="list-group list-group-flush serverlist">
@foreach (var service in Model.Containers)
{
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>@service.Name</span>
<span class="badge @(service.Status == "Running" ? "bg-success" : "bg-warning")">
@service.Status
</span>
</li>
}
</ul>
</div>
</div>
<!-- Server Liste -->
<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)
{
<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%
</span>
<span class="badge bg-info")">
RAM: 65.09%
</span>
<span class="badge @(server.IsOnline ? "bg-success" : "bg-danger")">
@(server.IsOnline ? "Online" : "Offline")
</span>
</li>
}
</ul>
</div>
</div>
</body>

View File

@@ -3,6 +3,10 @@
ViewData["Title"] = "Neuen Server hinzufügen"; ViewData["Title"] = "Neuen Server hinzufügen";
} }
<head>
<link rel="stylesheet" href="~/css/main.css" />
</head>
<div class="container mt-5" style="max-width: 700px;"> <div class="container mt-5" style="max-width: 700px;">
<div class="card shadow rounded-3 p-4"> <div class="card shadow rounded-3 p-4">
<h1 class="mb-4 text-primary-emphasis"> <h1 class="mb-4 text-primary-emphasis">

View File

@@ -3,76 +3,82 @@
ViewData["Title"] = "Serverübersicht"; ViewData["Title"] = "Serverübersicht";
} }
<head>
<link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="~/css/server-detail.css" />
</head>
<div id="server-cards-container"> <div id="server-cards-container">
<div class="container mt-4"></div> <div class="container mt-4">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="bi bi-hdd-network me-2 text-primary"></i>Serverdetails: @Model.Name <i class="bi bi-hdd-network me-2 text-primary"></i>@Model.Name
</h5> </h5>
<span class="badge @(Model.IsOnline ? "bg-success" : "bg-danger")"> <span class="badge @(Model.IsOnline ? "bg-success" : "bg-danger")">
<i class="bi @(Model.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i> <i class="bi @(Model.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i>
@(Model.IsOnline ? "Online" : "Offline") @(Model.IsOnline ? "Online" : "Offline")
</span> </span>
</div> </div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-3">ID</dt>
<dd class="col-sm-9">@Model.Id</dd>
<dt class="col-sm-3">IP-Adresse</dt> <div class="infocard row g-4 mb-4">
<dd class="col-sm-9">@Model.IPAddress</dd> <div class="info col-6 text-text col-lg-3">
<div><i class="bi bi-globe me-1"></i><strong>IP:</strong> @Model.IPAddress</div>
<div><i class="bi bi-pc-display me-1"></i><strong>Typ:</strong> @Model.Type</div>
<div><i class="bi bi-calendar-check me-1"></i><strong>Erstellt:</strong>
@Model.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</div>
<div><i class="bi bi-clock me-1"></i><strong>Last-Seen:</strong>
@Model.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</div>
</div>
<div class="hardware col-6 text-text col-lg-3">
<div><i class="bi bi-cpu me-1"></i><strong>CPU:</strong> @(Model.CpuType ?? "not found") </div>
<div><i class="bi bi-cpu me-1"></i><strong>CPU-Kerne: </strong> @Model.CpuCores </div>
<div><i class="bi bi-gpu-card me-1"></i><strong>GPU:</strong> @(Model.GpuType ?? "not found")
</div>
<div><i class="bi bi-memory me-1"></i><strong>RAM:</strong> @(Model.RamSize) </div>
<div><i class="bi bi-hdd me-1"></i><strong>Disk Space:</strong> ... </div>
</div>
<div class="hardware col-6 text-text col-lg-3">
<div class="card-footer text-end">
<a asp-action="EditServer" asp-route-id="@Model.Id" class="btn btn-outline-primary me-2">
<i class="bi bi-pencil"></i> Bearbeiten
</a>
<form asp-action="Delete" asp-route-id="@Model.Id" method="post" class="d-inline"
onsubmit="return confirm('Diesen Server wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Löschen
</button>
</form>
</div>
</div>
<dt class="col-sm-3">Typ</dt> </div>
<dd class="col-sm-9">@Model.Type</dd>
<dt class="col-sm-3">Erstellt am</dt>
<dd class="col-sm-9">@Model.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</dd>
<dt class="col-sm-3">Zuletzt gesehen</dt>
<dd class="col-sm-9">@Model.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</dd>
</dl>
</div>
<div class="card-footer text-end">
<a asp-action="EditServer" asp-route-id="@Model.Id" class="btn btn-outline-primary me-2">
<i class="bi bi-pencil"></i> Bearbeiten
</a>
<form asp-action="Delete" asp-route-id="@Model.Id" method="post" class="d-inline"
onsubmit="return confirm('Diesen Server wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Löschen
</button>
</form>
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<h6><i class="bi bi-graph-up me-1"></i>CPU Last</h6> <h6><i class="bi bi-graph-up me-1"></i>CPU Last</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 500px; width: 100%"> <div class="graphcontainer p-4 text-center text-muted">
<canvas id="cpuUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas> <canvas class="graph" id="cpuUsageChart"></canvas>
</div> </div>
</div> </div>
<div class="mt-4">
</div>
<h6><i class="bi bi-graph-up me-1"></i>RAM Last</h6> <h6><i class="bi bi-graph-up me-1"></i>RAM Last</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 500px; width: 100%"> <div class="graphcontainer p-4 text-center text-muted">
<canvas id="ramUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas> <canvas class="graph" id="ramUsageChart"></canvas>
</div> </div>
</div> </div>
<div class="mt-4">
<div class="mt-4"></div>
<h6><i class="bi bi-graph-up me-1"></i>GPU Last</h6> <h6><i class="bi bi-graph-up me-1"></i>GPU Last</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 500px; width: 100%"> <div class="graphcontainer p-4 text-center text-text">
<canvas id="gpuUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas> <canvas class="graph" id="gpuUsageChart"></canvas>
</div> </div>
</div> </div>
</div> </div>
</div>
@section Scripts { @section Scripts {
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> <script>
@@ -88,7 +94,6 @@
datasets: [{ datasets: [{
label: 'CPU Last (%)', label: 'CPU Last (%)',
data: [], data: [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)', backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true, fill: true,
tension: 0.3, tension: 0.3,
@@ -109,7 +114,7 @@
}, },
x: { x: {
title: { title: {
display: true, display: false,
text: 'Zeit' text: 'Zeit'
} }
} }
@@ -124,7 +129,6 @@
datasets: [{ datasets: [{
label: 'RAM Last (%)', label: 'RAM Last (%)',
data: [], data: [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)', backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true, fill: true,
tension: 0.3, tension: 0.3,
@@ -140,12 +144,12 @@
max: 100, max: 100,
title: { title: {
display: true, display: true,
text: 'Prozent' text: 'Auslastung in %'
} }
}, },
x: { x: {
title: { title: {
display: true, display: false,
text: 'Zeit' text: 'Zeit'
} }
} }
@@ -160,7 +164,6 @@
datasets: [{ datasets: [{
label: 'GPU Last (%)', label: 'GPU Last (%)',
data: [], data: [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)', backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true, fill: true,
tension: 0.3, tension: 0.3,
@@ -176,12 +179,12 @@
max: 100, max: 100,
title: { title: {
display: true, display: true,
text: 'Prozent' text: 'Auslastung in %'
} }
}, },
x: { x: {
title: { title: {
display: true, display: false,
text: 'Zeit' text: 'Zeit'
} }
} }

View File

@@ -4,31 +4,36 @@
ViewData["Title"] = "Server bearbeiten"; ViewData["Title"] = "Server bearbeiten";
} }
<head>
<link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="~/css/server-edit.css" />
</head>
<h2 class="mb-4">Server bearbeiten</h2> <h2 class="mb-4">Server bearbeiten</h2>
<form asp-action="EditServer" asp-controller="Server" method="post"> <form asp-action="EditServer" asp-controller="Server" method="post">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<input type="hidden" asp-for="Id" /> <input type="hidden" asp-for="Id" />
<div class="card mb-4"> <div class="card mb-4 g-4">
<div class="card-header"> <div class="card-header">
<h4 class="mb-0">Allgemeine Informationen</h4> <h4 class="mb-0">Allgemeine Informationen</h4>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="mb-3"> <div class="mb-3">
<label asp-for="Name" class="form-label"></label> <label asp-for="Name" class="form-label">Name</label>
<input asp-for="Name" class="form-control" /> <input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span> <span asp-validation-for="Name" class="text-danger"></span>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label asp-for="IPAddress" class="form-label"></label> <label asp-for="IPAddress" class="form-label">IP-Adresse</label>
<input asp-for="IPAddress" class="form-control" /> <input asp-for="IPAddress" class="form-control" />
<span asp-validation-for="IPAddress" class="text-danger"></span> <span asp-validation-for="IPAddress" class="text-danger"></span>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label asp-for="Type" class="form-label"><i class="bi bi-hdd-network me-1"></i>Typ</label> <label asp-for="Type" class="form-label">Typ</label>
<select asp-for="Type" class="form-select"> <select asp-for="Type" class="form-select">
<option>VPS</option> <option>VPS</option>
<option>VM</option> <option>VM</option>
@@ -176,6 +181,6 @@
<div class="d-flex justify-content-end gap-2"> <div class="d-flex justify-content-end gap-2">
<button type="submit" class="btn btn-primary"><i class="bi bi-save me-1"></i>Speichern</button> <button type="submit" class="btn btn-primary"><i class="bi bi-save me-1"></i>Speichern</button>
<a asp-action="Overview" class="btn btn-secondary"><i class="bi bi-x-circle me-1"></i>Abbrechen</a> <a asp-action="Overview" class="btn btn-danger"><i class="bi bi-x-circle me-1"></i>Abbrechen</a>
</div> </div>
</form> </form>

View File

@@ -4,61 +4,45 @@
<div class="row g-4"> <div class="row g-4">
@foreach (var s in Model) @foreach (var s in Model)
{ {
<div class="col-12 col-sm-6"> <div class="col-12">
<div class="card h-100 border-secondary shadow-sm"> <div class="card h-100 border-secondary shadow-sm">
<div class="card-body d-flex flex-column gap-3"> <div class="card-body d-flex flex-column gap-3">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="card-title text-dark mb-0"> <h5 class="card-title text-text mb-0">
<i class="bi bi-pc-display me-2 text-muted"></i>(#@s.Id) @s.Name <i class="bi bi-pc-display me-2 text-text"></i>(#@s.Id) @s.Name
</h5> </h5>
<span class="badge
@(s.IsOnline ? "bg-success text-light" : "bg-danger text-light")"> <div class="col-md-4 text-text small">
<div><i class="bi bi-globe me-1"></i><strong>IP:</strong> @s.IPAddress</div>
<div><i class="bi bi-pc-display me-1"></i><strong>Typ:</strong> @s.Type</div>
</div>
<span class="badge @(s.IsOnline ? "bg-success text-light" : "bg-danger text-light")">
<i class="bi @(s.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i> <i class="bi @(s.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i>
@(s.IsOnline ? "Online" : "Offline") @(s.IsOnline ? "Online" : "Offline")
</span> </span>
</div> <div class="d-flex flex-wrap gap-4">
<a asp-action="EditServer" asp-route-id="@s.Id" class="btn btn-outline-primary">
<i class="bi bi-pencil-square me-1"></i> Bearbeiten
</a>
<a asp-asp-controller="Server" asp-action="Details" asp-route-id="@s.Id"
class="btn btn-outline-primary">
<i class="bi bi-bar-chart-fill me-1"></i> Metrics
</a>
<form asp-action="Delete" asp-controller="Server" asp-route-id="@s.Id" method="post"
onsubmit="return confirm('Diesen Server wirklich löschen?');" class="m-0">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash me-1"></i> Löschen
</button>
</form>
<div class="row mb-3">
<div class="col-md-5 text-muted small">
<div><i class="bi bi-globe me-1"></i><strong>IP:</strong> @s.IPAddress</div>
<div><i class="bi bi-pc-display me-1"></i><strong>Typ:</strong> @s.Type</div>
<div><i class="bi bi-calendar-check me-1"></i><strong>Erstellt:</strong>
@s.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</div>
<div><i class="bi bi-clock me-1"></i><strong>Last-Seen:</strong>
@s.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</div>
<div><i class="bi bi-cpu me-1"></i><strong>CPU:</strong> @(s.CpuType ?? "not found") </div>
<div><i class="bi bi-cpu me-1"></i><strong>CPU-Kerne: </strong> @s.CpuCores </div>
<div><i class="bi bi-gpu-card me-1"></i><strong>GPU:</strong> @(s.GpuType ?? "not found")
</div>
<div><i class="bi bi-memory me-1"></i><strong>RAM:</strong> @(s.RamSize) </div>
<div><i class="bi bi-hdd me-1"></i><strong>Disk Space:</strong> ... </div>
</div> </div>
</div> </div>
<div class="d-flex flex-wrap gap-2">
<a asp-action="EditServer" asp-route-id="@s.Id" class="btn btn-outline-primary">
<i class="bi bi-pencil-square me-1"></i> Bearbeiten
</a>
<a asp-asp-controller="Server" asp-action="Details" asp-route-id="@s.Id"
class="btn btn-outline-primary">
<i class="bi bi-bar-chart-fill me-1"></i> Metrics
</a>
<a asp-asp-controller="Container" asp-action="Overview" asp-route-id="@s.Id"
class="btn btn-outline-primary">
<i class="bi bi-box-fill me-1"></i> Container
</a>
<form asp-action="Delete" asp-controller="Server" asp-route-id="@s.Id" method="post"
onsubmit="return confirm('Diesen Server wirklich löschen?');" class="m-0">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash me-1"></i> Löschen
</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,21 +3,31 @@
ViewData["Title"] = "Serverübersicht"; ViewData["Title"] = "Serverübersicht";
} }
<head>
<link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="~/css/server-overview.css" />
</head>
<div class="d-flex align-items-center justify-content-between mb-4"> <div class="d-flex align-items-center justify-content-between mb-4">
<h1 class="h2 fw-bold mb-0">
<i class="bi bi-hdd-network me-2 text-primary"></i>Serverübersicht <h1 class="mb-4"">
<i class="bi bi-hdd-network"></i> Serverübersicht
</h1> </h1>
<form asp-action="AddServer" method="get" asp-controller="Server">
<button type="submit" class="btn btn-primary"> <a class="nav-link" href="/Server/addServer">
<i class="bi"></i> neuen Server erstellen <button class="btn btn-primary"> Server hnzufügen </button>
</button> </a>
</form>
</div> </div>
<div id="server-cards-container"> <div id="server-cards-container">
@await Html.PartialAsync("_ServerCard", Model.Servers) @await Html.PartialAsync("_ServerCard", Model.Servers)
</div> </div>
@section Scripts { @section Scripts {
<script> <script>
async function loadServerCards() { async function loadServerCards() {

View File

@@ -14,7 +14,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>@ViewData["Title"] - Watcher</title> <title>@ViewData["Title"] - Watcher</title>
<link rel="stylesheet" href="~/css/site.css" /> <link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
@@ -24,12 +24,12 @@
} }
.sidebar { .sidebar {
width: 240px; width: 15rem;
height: 100vh; height: 100vh;
position: fixed; position: fixed;
background-color: #212121;
left: 0; left: 0;
top: 0; top: 0;
background-color: #343a40;
color: white; color: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -71,16 +71,22 @@
<a class="nav-link" href="/">Dashboard</a> <a class="nav-link" href="/">Dashboard</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#">Netzwerk</a>
</li>
<li class="nav-item"></li>
<a class="nav-link" href="/Server/Overview">Servers</a> <a class="nav-link" href="/Server/Overview">Servers</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/Container/Overview">Container</a> <a class="nav-link" href="/Container/Overview">Services</a>
</li> </li>
<!-- Noch nicht implementiert <!-- Noch nicht implementiert
<li class="nav-item"></li> <li class="nav-item"></li>
<a class="nav-link" href="/Uptime/Overview">Uptime</a> <a class="nav-link" href="/Uptime/Overview">Uptime</a>
</li> </li>
--> -->
<li class="nav-item">
<a class="nav-link" href="/System/Settings">Einstellungen</a>
</li>
</ul> </ul>
</div> </div>
@@ -92,6 +98,7 @@
<div class="rounded-circle bg-secondary text-white px-2 py-1"> <div class="rounded-circle bg-secondary text-white px-2 py-1">
<i class="bi bi-person"></i> <i class="bi bi-person"></i>
</div> </div>
<div> <div>
<strong>@User.Identity?.Name</strong><br /> <strong>@User.Identity?.Name</strong><br />
<small class="text-muted">Profil ansehen</small> <small class="text-muted">Profil ansehen</small>

View File

@@ -1,53 +0,0 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
html, body {
min-height: 100vh;
overflow-y: auto;
}
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

View File

@@ -8,9 +8,11 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Login</title> <title>@ViewData["Title"] - Login</title>
<link rel="stylesheet" href="~/css/main.css" />
<link rel="stylesheet" href="~/css/Login.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" />
</head> </head>
<body class="bg-light d-flex align-items-center justify-content-center" style="min-height: 100vh;"> <body class="d-flex align-items-center justify-content-center" style="min-height: 100vh;">
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">

View File

@@ -2,87 +2,47 @@
ViewData["Title"] = "Settings"; ViewData["Title"] = "Settings";
var pictureUrl = User.Claims.FirstOrDefault(c => c.Type == "picture")?.Value ?? ""; var pictureUrl = User.Claims.FirstOrDefault(c => c.Type == "picture")?.Value ?? "";
var preferredUsername = User.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value ?? "admin"; var preferredUsername = User.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value ?? "admin";
var isLocalUser = ViewBag.IdentityProvider == "local";
var DbEngine = ViewBag.DbProvider; var DbEngine = ViewBag.DbProvider;
var mail = ViewBag.mail;
var ServerVersion = ViewBag.ServerVersion;
} }
<style> <head>
.Settingscontainer { <link rel="stylesheet" href="~/css/site.css" />
display: flex; <link rel="stylesheet" href="~/css/settings.css" />
flex-wrap: wrap; </head>
/* Wichtig: erlaubt Umbruch */
gap: 1rem;
/* optionaler Abstand */
}
.Settingscontainer>* {
flex: 1 1 calc(50% - 0.5rem);
/* 2 Elemente pro Zeile, inkl. Gap */
box-sizing: border-box;
}
</style>
<div class="Settingscontainer"> <div class="Settingscontainer">
@if (isLocalUser)
{
<div class="card shadow mt-5 p-4" style="width: 40%; margin: auto;">
<h4><i class="bi bi-pencil-square me-2"></i>Benutzerdaten ändern</h4>
<form asp-action="Edit" method="post" asp-controller="User">
<div class="mb-3">
<label for="Username" class="form-label">Neuer Benutzername</label>
<input type="text" class="form-control" id="Username" name="Username" value="@preferredUsername" />
</div>
<div class="mb-3">
<label for="NewPassword" class="form-label">Neues Passwort</label>
<input type="password" class="form-control" id="NewPassword" name="NewPassword" />
</div>
<div class="mb-3">
<label for="ConfirmPassword" class="form-label">Passwort bestätigen</label>
<input type="password" class="form-control" id="ConfirmPassword" name="ConfirmPassword" />
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-save me-1"></i>Speichern
</button>
</form>
</div>
}
else
{
<div class="alert alert-info mt-4 text-center" style="width: 40%; margin: auto;">
<i class="bi bi-info-circle me-1"></i>Benutzerdaten können nur für lokal angemeldete Nutzer geändert werden.
</div>
}
<div class="card shadow mt-5 p-4" style="width: 55%; margin: auto;"> <div class="card shadow mt-5 p-4" style="width: 55%; margin: auto;">
<h4><i class="bi bi-pencil-square me-2"></i>Systemeinformationen</h4> <h4><i class="bi bi-pencil-square me-2"></i>Systemeinformationen</h4>
<br> <br>
<h5>Watcher Version: v0.1.0</h5> <h5>Watcher Version: @ServerVersion</h5>
<hr class="my-4" /> <hr class="my-4" />
<h5>Authentifizierungsmethode: </h5> <h5>Authentifizierungsmethode: <strong>@(ViewBag.IdentityProvider ?? "nicht gefunden")</strong></h5>
<p><strong>@(ViewBag.IdentityProvider ?? "nicht gefunden")</strong></p>
<hr class="my-4" /> <hr class="my-4" />
<h5>Datenbank-Engine: </h5> <h5>Datenbank-Engine: <strong>@(DbEngine ?? "nicht gefunden")</strong></h5>
<strong>@(DbEngine ?? "nicht gefunden")</strong>
<!-- Falls Sqlite verwendet wird können Backups erstellt werden --> <!-- Falls Sqlite verwendet wird können Backups erstellt werden -->
@if (DbEngine == "Microsoft.EntityFrameworkCore.Sqlite") @if (DbEngine == "Microsoft.EntityFrameworkCore.Sqlite")
{ {
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<form asp-action="CreateSqlDump" method="post" asp-controller="Database"> <form asp-action="CreateSqlDump" method="post" asp-controller="Database">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-db">
<i class="bi bi-save me-1"></i> Backup erstellen <i class="bi bi-save me-1"></i> Backup erstellen
</button> </button>
</form> </form>
<form asp-action="ManageSqlDumps" method="post" asp-controller="Database"> <form asp-action="ManageSqlDumps" method="post" asp-controller="Database">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-db">
<i class="bi bi-save me-1"></i> Backups verwalten <i class="bi bi-save me-1"></i> Backups verwalten
</button> </button>
</form> </form>
@@ -111,9 +71,8 @@
</div> </div>
<div class="card shadow mt-5 p-4" style="width: 55%; margin: auto;"> <div class="card shadow mt-5 p-4" style="width: 55%; margin: auto;">
<h4><i class="bi bi-pencil-square me-2"></i>Systemeinstellungen</h4> <h4><i class="bi bi-pencil-square me-2"></i>Benachrichtungen</h4>
<h5>Benachrichtigungen: </h5>
<p>Registrierte E-Mail Adresse: <strong>@(ViewBag.mail ?? "nicht gefunden")</strong></p> <p>Registrierte E-Mail Adresse: <strong>@(ViewBag.mail ?? "nicht gefunden")</strong></p>
<!-- action="/Notification/UpdateSettings" existiert noch nicht--> <!-- action="/Notification/UpdateSettings" existiert noch nicht-->
@@ -143,7 +102,7 @@
<label class="form-check-label" for="notifyMaintenance">Wartungsinformationen erhalten</label> <label class="form-check-label" for="notifyMaintenance">Wartungsinformationen erhalten</label>
</div> </div>
<button type="submit" class="btn btn-primary mt-3"> <button type="submit" class="btn btn-db mt-3">
<i class="bi bi-save me-1"></i>Speichern <i class="bi bi-save me-1"></i>Speichern
</button> </button>
</div> </div>

View File

@@ -4,14 +4,20 @@
var Id = ViewBag.Id; var Id = ViewBag.Id;
var preferredUsername = ViewBag.name; var preferredUsername = ViewBag.name;
var IdProvider = ViewBag.IdProvider; var IdProvider = ViewBag.IdProvider;
var mail = ViewBag.mail;
} }
<head>
<link rel="stylesheet" href="~/css/site.css" />
<link rel="stylesheet" href="~/css/user-info.css" />
</head>
<div class="container mt-5"> <div class="container mt-5">
<div class="card shadow-lg rounded-3 p-4" style="max-width: 700px; margin: auto;"> <div class="card shadow-lg rounded-3 p-4" style="max-width: 700px; margin: auto;">
<div class="text-center mb-4"> <div class="text-center mb-4">
@if (!string.IsNullOrEmpty(pictureUrl)) @if (!string.IsNullOrEmpty(pictureUrl))
{ {
<img src="@pictureUrl" alt="Profilbild" class="rounded-circle shadow" style="width: 120px; height: 120px; object-fit: cover;" /> <img src="@pictureUrl" alt="Profilbild" class="rounded-circle shadow picture" />
} }
else else
{ {
@@ -21,12 +27,12 @@
</div> </div>
} }
<h3 class="mt-3"> <h3 class="mt-3">
<i class="bi bi-person-circle me-1"></i>@preferredUsername <i class="bi"></i>@preferredUsername
</h3> </h3>
</div> </div>
<table class="table table-hover"> <table class="usertable">
<tbody> <tbody>
<tr> <tr>
<th><i class="bi bi-person-badge me-1"></i>Username</th> <th><i class="bi bi-person-badge me-1"></i>Username</th>
@@ -34,11 +40,11 @@
</tr> </tr>
<tr> <tr>
<th><i class="bi bi-envelope me-1"></i>E-Mail</th> <th><i class="bi bi-envelope me-1"></i>E-Mail</th>
<td>@(ViewBag.Mail ?? "Nicht verfügbar")</td> <td>@(mail ?? "Nicht verfügbar")</td>
</tr> </tr>
<tr> <tr>
<th><i class="bi bi-fingerprint me-1"></i>Benutzer-ID</th> <th><i class="bi bi-fingerprint me-1"></i>Benutzer-ID</th>
<td>@(ViewBag.Id ?? "Nicht verfügbar")</td> <td>@(Id ?? "Nicht verfügbar")</td>
</tr> </tr>
@if(IdProvider != "local") @if(IdProvider != "local")
{ {
@@ -83,12 +89,30 @@
</tbody> </tbody>
</table> </table>
<div>
<form method="get" asp-controller="User" asp-action="UserSettings" class="text-center mt-4">
<button type="submit" class="btn btn-info"> <div class="card shadow mt-5 p-4" style="width: 100%; margin: auto;">
<i class="bi bi-gear-wide-connected me-1"></i>Einstellungen <h4><i class="bi bi-pencil-square me-2"></i>Benutzerdaten ändern</h4>
<form asp-action="Edit" method="post" asp-controller="User">
<div class="mb-3">
<label for="Username" class="form-label">Neuer Benutzername</label>
<input type="text" class="form-control" id="Username" name="Username" value="@preferredUsername" />
</div>
<div class="mb-3">
<label for="NewPassword" class="form-label">Neues Passwort</label>
<input type="password" class="form-control" id="NewPassword" name="NewPassword" />
</div>
<div class="mb-3">
<label for="ConfirmPassword" class="form-label">Passwort bestätigen</label>
<input type="password" class="form-control" id="ConfirmPassword" name="ConfirmPassword" />
</div>
<button type="submit" class="btn btn-db">
<i class="bi bi-save me-1"></i>Speichern
</button> </button>
</form> </form>
</div>
<div>
<form method="post" asp-controller="Auth" asp-action="Logout" class="text-center mt-4"> <form method="post" asp-controller="Auth" asp-action="Logout" class="text-center mt-4">
<button type="submit" class="btn btn-danger"> <button type="submit" class="btn btn-danger">
<i class="bi bi-box-arrow-right me-1"></i>Abmelden <i class="bi bi-box-arrow-right me-1"></i>Abmelden

Binary file not shown.

View File

View File

@@ -0,0 +1,24 @@
.login-card {
background-color: var(--color-surface);
color: var(--color-text);
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
max-width: 500px;
margin: auto;
}
.btn-oidc {
background-color: var(--color-primary);
color: white;
border: none;
}
.btn-oidc:hover {
background-color: var(--color-primary);
}
.form-error {
color: #ff6b6b;
font-size: 0.875rem;
}

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,4 @@
.serverlist {
background-color: var(--color-surface);
color: var(--color-text);
}

View File

@@ -0,0 +1,13 @@
.dumptable {
width: 100%;
border: .2rem solid;
margin: 1rem;
padding: 1rem
}
.dumptable thead {
margin: 1rem;
border: .2rem solid;
}

View File

@@ -0,0 +1,67 @@
:root {
--color-bg: #141414;
--color-surface: #212121;
--color-accent: #00e5ff;
--color-primary: #616161;
--color-text: #f9feff;
--color-muted: #c0c0c0;
--color-success: #14a44d;
--color-danger: #ff6b6b;
--color-warning: #CBE013;
}
body {
background-color: var(--color-bg);
color: var(--color-primary);
}
a {
color: var(--color-primary);
}
.card, .navbar, .form-control, .dropdown-menu {
background-color: var(--color-surface);
color: var(--color-text);
border: none;
}
.form-control {
background-color: var(--color-muted);
color: var(--color-text);
}
.form-control:active {
background-color: var(--color-muted);
color: var(--color-text);
}
.form-control::placeholder {
color: var(--color-muted);
}
.navbar .nav-link,
.dropdown-item {
color: var(--color-text);
}
.btn-primary {
background-color: var(--color-accent);
border: none;
}
.btn-primary:hover {
background-color: var(--color-accent);
}
.btn-danger {
background-color: var(--color-danger);
border: none;
}
.table {
color: var(--color-text);
}
hr {
border-top: 1px solid var(--color-accent);
}

View File

@@ -0,0 +1,20 @@
.info {
margin: 2rem;
margin-top: 3rem;
}
.hardware {
margin: 2rem;
margin-top: 3rem;
}
.graphcontainer {
height: 25rem;
width: 100%;
background-color: var(--color-surface);
}
.graph {
width: 100%;
height: 22rem;
}

View File

View File

View File

@@ -0,0 +1,12 @@
.ServiceList {
width: 80%;
}
.ServiceRow {
border-style: solid;
border-color: var(--color-text);
}
.ServiceEntry {
text-decoration: none;
}

View File

@@ -0,0 +1,24 @@
.Settingscontainer {
display: flex;
flex-wrap: wrap;
/* Wichtig: erlaubt Umbruch */
gap: 1rem;
/* optionaler Abstand */
}
.Settingscontainer>* {
flex: 1 1 calc(50% - 0.5rem);
/* 2 Elemente pro Zeile, inkl. Gap */
box-sizing: border-box;
}
.btn-db {
background-color: var(--color-primary);
border: none;
}
.btn-db:hover {
background-color: var(--color-accent);
border: none;
}

View File

@@ -1,9 +1,9 @@
:root { :root {
--color-bg: #0d1b2a; --color-bg: #141414;
--color-surface: #1b263b; --color-surface: #212121;
--color-accent: #415a77; --color-accent: #415a77;
--color-primary: #0d6efd; --color-primary: white;
--color-text: #ffffff; --color-text: #f9feff;
--color-muted: #c0c0c0; --color-muted: #c0c0c0;
--color-success: #14a44d; --color-success: #14a44d;
--color-danger: #ff6b6b; --color-danger: #ff6b6b;
@@ -11,7 +11,7 @@
body { body {
background-color: var(--color-bg); background-color: var(--color-bg);
color: var(--color-text); color: var(--color-primary);
} }
a { a {
@@ -25,7 +25,7 @@ a {
} }
.form-control { .form-control {
background-color: var(--color-accent); background-color: var(--color-bg);
color: var(--color-text); color: var(--color-text);
} }
@@ -57,10 +57,6 @@ a {
background-color: #0f8c3c; background-color: #0f8c3c;
} }
.table {
color: var(--color-text);
}
hr { hr {
border-top: 1px solid var(--color-accent); border-top: 1px solid var(--color-accent);
} }

View File

@@ -0,0 +1,39 @@
.table {
color: red;
}
.picture {
width: 120px;
height: 120px;
object-fit: cover;
}
.usertable {
background-color: var(--color-surface);
color: var(--color-text);
}
.Settingscontainer {
display: flex;
flex-wrap: wrap;
/* Wichtig: erlaubt Umbruch */
gap: 1rem;
/* optionaler Abstand */
}
.Settingscontainer>* {
flex: 1 1 calc(50% - 0.5rem);
/* 2 Elemente pro Zeile, inkl. Gap */
box-sizing: border-box;
}
.btn-db {
background-color: var(--color-primary);
border: none;
}
.btn-db:hover {
background-color: var(--color-accent);
border: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1,18 +1,16 @@
services: services:
watcher: watcher:
image: git.triggermeelmo.com/daniel-hbn/watcher/watcher:development image: git.triggermeelmo.com/watcher/watcher-server:v0.1.0
container_name: watcher container_name: watcher
deploy:
resources:
limits:
memory: 200M
restart: unless-stopped restart: unless-stopped
env_file: .env env_file: .env
ports: ports:
- "5000:5000" - "5000:5000"
volumes: volumes:
- ./data:/app/persistence - ./watcher-volumes/data:/app/persistence
- ./dumps:/app/wwwroot/downloads/sqlite - ./watcher-volumes/dumps:/app/wwwroot/downloads/sqlite
- ./logs:/app/logs - ./watcher-volumes/logs:/app/logs
healthcheck:
test: "curl -f http://localhost:5000"
interval: 1m30s
timeout: 30s
retries: 5
start_period: 30s