Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
db45872908 | |||
8e0dcc34e7 | |||
015cdcb202 | |||
2841752870 | |||
8ffb220634 | |||
120374ebe1 | |||
55aa9f546a | |||
0974c1d7dd | |||
0e72287c6b | |||
3918425ef9 | |||
ec13a51575 | |||
b8626cb8ea | |||
281e9c686b | |||
69dd73a079 | |||
2e9d41fe60 | |||
7e75f3e49e | |||
8e362f7271 | |||
df7674f063 | |||
9d0a2e40be | |||
c8dc8adb0d | |||
0aacf369d7 | |||
2d8bf648d9 | |||
85c5a80360 | |||
36e16fbcf9 | |||
52c4243efc | |||
6248fad147 | |||
5e2f9e4c3c | |||
32a6c0d108 | |||
d1f348a9fa | |||
cff9a6699c | |||
0bb0c09ce3 | |||
5c9c9cd165 | |||
b481cd764e | |||
f4fa055edf | |||
7def038cc9 | |||
6e6d17b134 | |||
ab6f99eb6b | |||
62f384cc7b | |||
c8480bb681 | |||
1c0fab71bb | |||
aa35e83f6b | |||
cd6d2a1825 |
132
.gitea/workflows/build.yaml
Normal file
132
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Gitea CI/CD
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "development", "main", "feature/*", "bugfix/*", "enhancement/*" ]
|
||||
tags: [ "v*.*.*" ]
|
||||
pull_request:
|
||||
branches: [ "development", "main" ]
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: '8.0.x'
|
||||
DOCKER_IMAGE_NAME: watcher-server
|
||||
REGISTRY_URL: git.triggermeelmo.com
|
||||
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' }}
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
continue-on-error: true
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish -c Release -o out
|
||||
|
||||
set-tag:
|
||||
name: Set Tag Name
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
outputs:
|
||||
tag_name: ${{ steps.set_tag.outputs.tag_name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Determine next semantic version tag
|
||||
id: set_tag
|
||||
run: |
|
||||
git fetch --tags
|
||||
|
||||
# Find latest tag matching vX.Y.Z
|
||||
latest_tag=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n 1)
|
||||
if [[ -z "$latest_tag" ]]; then
|
||||
major=0
|
||||
minor=0
|
||||
patch=0
|
||||
else
|
||||
version="${latest_tag#v}"
|
||||
IFS='.' read -r major minor patch <<< "$version"
|
||||
fi
|
||||
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
major=$((major + 1))
|
||||
minor=0
|
||||
patch=0
|
||||
elif [[ "${GITHUB_REF}" == "refs/heads/development" ]]; then
|
||||
minor=$((minor + 1))
|
||||
patch=0
|
||||
else
|
||||
patch=$((patch + 1))
|
||||
fi
|
||||
|
||||
new_tag="v${major}.${minor}.${patch}"
|
||||
echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT
|
||||
|
||||
docker-build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
|
||||
needs: [build-and-test, set-tag]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY_URL}}
|
||||
username: ${{ secrets.AUTOMATION_USERNAME }}
|
||||
password: ${{ secrets.AUTOMATION_PASSWORD }}
|
||||
|
||||
- name: Build and Push Multi-Arch Docker Image
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform ${{ env.DOCKER_PLATFORMS }} \
|
||||
-t ${{ env.REGISTRY_URL }}/watcher/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.set-tag.outputs.tag_name }} \
|
||||
--push .
|
||||
|
||||
tag:
|
||||
name: Create Tag
|
||||
needs: [docker-build, build, set-tag]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Git user
|
||||
run: |
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
|
||||
- name: Create and push tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git tag ${{ needs.set-tag.outputs.tag_name }}
|
||||
git push origin ${{ needs.set-tag.outputs.tag_name }}
|
@@ -1,62 +0,0 @@
|
||||
name: Development Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- development
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: '8.0.x'
|
||||
DOCKER_IMAGE_NAME: 'watcher-server'
|
||||
REGISTRY_URL: 'git.triggermeelmo.com/watcher'
|
||||
DOCKER_PLATFORMS: 'linux/amd64,linux/arm64'
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
continue-on-error: true
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish -c Release -o out
|
||||
|
||||
docker-build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: git.triggermeelmo.com
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and Push Multi-Arch Docker Image
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform ${{ env.DOCKER_PLATFORMS }} \
|
||||
-t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:development \
|
||||
-t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} \
|
||||
--push .
|
@@ -1,62 +0,0 @@
|
||||
name: Release Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: '8.0.x'
|
||||
DOCKER_IMAGE_NAME: 'watcher-server'
|
||||
REGISTRY_URL: 'git.triggermeelmo.com/watcher'
|
||||
DOCKER_PLATFORMS: 'linux/amd64,linux/arm64'
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
continue-on-error: true
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish -c Release -o out
|
||||
|
||||
docker-build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: git.triggermeelmo.com
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and Push Multi-Arch Docker Image
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform ${{ env.DOCKER_PLATFORMS }} \
|
||||
-t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:v0.1.0 \
|
||||
-t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} \
|
||||
--push .
|
@@ -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
|
||||
|
32
Watcher/Controllers/ApiController.cs
Normal file
32
Watcher/Controllers/ApiController.cs
Normal 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();
|
||||
}
|
||||
}
|
@@ -116,7 +116,7 @@ namespace Watcher.Controllers
|
||||
|
||||
TempData["DumpMessage"] = "SQLite-Dump erfolgreich erstellt.";
|
||||
_logger.LogInformation("SQLite-Dump erfolgreich erstellt.");
|
||||
return RedirectToAction("UserSettings", "User");
|
||||
return RedirectToAction("Settings", "System");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
// Local Namespaces
|
||||
using Watcher.Data;
|
||||
using Watcher.ViewModels;
|
||||
using Watcher.Services;
|
||||
|
||||
namespace Watcher.Controllers
|
||||
{
|
||||
@@ -17,11 +18,16 @@ namespace Watcher.Controllers
|
||||
// Logging einbinden
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
// Daten der Backgroundchecks abrufen
|
||||
private IDashboardStore _DashboardStore;
|
||||
|
||||
// HomeController Constructor
|
||||
public HomeController(AppDbContext context, ILogger<HomeController> logger)
|
||||
public HomeController(AppDbContext context, ILogger<HomeController> logger, IDashboardStore dashboardStore)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_DashboardStore = dashboardStore;
|
||||
|
||||
}
|
||||
|
||||
// Dashboard unter /home/index
|
||||
@@ -41,15 +47,27 @@ namespace Watcher.Controllers
|
||||
OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
|
||||
RunningContainers = 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(),
|
||||
NetworkStatus = _DashboardStore.NetworkStatus,
|
||||
DatabaseStatus = _DashboardStore.DatabaseStatus
|
||||
};
|
||||
|
||||
//ViewBag.NetworkConnection = _NetworkCheckStore.NetworkStatus;
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
// 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.
|
||||
public IActionResult DashboardStats()
|
||||
public async Task<IActionResult> DashboardStats()
|
||||
{
|
||||
var servers = _context.Servers.ToList();
|
||||
var containers = _context.Containers.ToList();
|
||||
@@ -58,18 +76,31 @@ namespace Watcher.Controllers
|
||||
|
||||
var model = new DashboardViewModel
|
||||
{
|
||||
ActiveServers = servers.Count(s => (now - s.LastSeen).TotalSeconds <= 120),
|
||||
OfflineServers = servers.Count(s => (now - s.LastSeen).TotalSeconds > 120),
|
||||
|
||||
//TODO: anwendbar, wenn Container implementiert wurden.
|
||||
//RunningContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds <= 120),
|
||||
//FailedContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds > 120),
|
||||
|
||||
LastLogin = DateTime.Now
|
||||
ActiveServers = await _context.Servers.CountAsync(s => s.IsOnline),
|
||||
OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
|
||||
RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning),
|
||||
FailedContainers = await _context.Containers.CountAsync(c => !c.IsRunning),
|
||||
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(),
|
||||
NetworkStatus = _DashboardStore.NetworkStatus,
|
||||
DatabaseStatus = _DashboardStore.DatabaseStatus
|
||||
};
|
||||
|
||||
return PartialView("_DashboardStats", model);
|
||||
}
|
||||
|
||||
public String ReturnNetworkStatus()
|
||||
{
|
||||
return "OK";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -55,26 +55,26 @@ public class MetricDto
|
||||
|
||||
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
|
||||
public double RAM_Size { get; set; } // GB
|
||||
public double RAM_Size { get; set; } // Bytes
|
||||
|
||||
public double RAM_Load { get; set; } // %
|
||||
|
||||
// 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
|
||||
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)
|
||||
{
|
||||
// neues Metric-Objekt erstellen
|
||||
var NewMetric = new Metric
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
ServerId = dto.ServerId,
|
||||
CPU_Load = dto.CPU_Load,
|
||||
CPU_Temp = dto.CPU_Temp,
|
||||
GPU_Load = dto.GPU_Load,
|
||||
GPU_Temp = dto.GPU_Temp,
|
||||
GPU_Vram_Size = dto.GPU_Vram_Size,
|
||||
GPU_Vram_Usage = dto.GPU_Vram_Usage,
|
||||
RAM_Load = dto.RAM_Load,
|
||||
RAM_Size = dto.RAM_Size,
|
||||
DISK_Size = dto.RAM_Size,
|
||||
DISK_Usage = dto.DISK_Usage,
|
||||
DISK_Temp = dto.DISK_Temp,
|
||||
NET_In = dto.NET_In,
|
||||
NET_Out = dto.NET_Out
|
||||
CPU_Load = sanitizeInput(dto.CPU_Load),
|
||||
CPU_Temp = sanitizeInput(dto.CPU_Temp),
|
||||
GPU_Load = sanitizeInput(dto.GPU_Load),
|
||||
GPU_Temp = sanitizeInput(dto.GPU_Temp),
|
||||
GPU_Vram_Size = calculateGigabyte(dto.GPU_Vram_Size),
|
||||
GPU_Vram_Usage = sanitizeInput(dto.GPU_Vram_Load),
|
||||
RAM_Load = sanitizeInput(dto.RAM_Load),
|
||||
RAM_Size = calculateGigabyte(dto.RAM_Size),
|
||||
DISK_Size = calculateGigabyte(dto.DISK_Size),
|
||||
DISK_Usage = calculateGigabyte(dto.DISK_Usage),
|
||||
DISK_Temp = sanitizeInput(dto.DISK_Temp),
|
||||
NET_In = calculateMegabit(dto.NET_In),
|
||||
NET_Out = calculateMegabit(dto.NET_Out)
|
||||
};
|
||||
try
|
||||
{
|
||||
// Metric Objekt in Datenbank einfügen
|
||||
_context.Metrics.Add(NewMetric);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
@@ -281,4 +283,40 @@ public class MonitoringController : Controller
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
36
Watcher/Controllers/SystemController.cs
Normal file
36
Watcher/Controllers/SystemController.cs
Normal 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(){}
|
||||
|
||||
}
|
@@ -55,6 +55,7 @@ public class UserController : Controller
|
||||
var user = _context.Users.FirstOrDefault(u => u.Username == username);
|
||||
if (user == null) return NotFound();
|
||||
|
||||
|
||||
var model = new EditUserViewModel
|
||||
{
|
||||
Username = user.Username
|
||||
@@ -90,31 +91,6 @@ public class UserController : Controller
|
||||
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
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
|
16
Watcher/Models/HealthStatus.cs
Normal file
16
Watcher/Models/HealthStatus.cs
Normal 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; }
|
||||
}
|
||||
}
|
@@ -1,14 +1,13 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Sqlite;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using Watcher.Data;
|
||||
using Watcher.Models;
|
||||
using Watcher.Services;
|
||||
//using Watcher.Services;
|
||||
//using Watcher.Workers;
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -17,7 +16,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// Serilog konfigurieren – nur Logs, die nicht von Microsoft stammen
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning) // <--
|
||||
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.File(
|
||||
"logs/watcher-.log",
|
||||
@@ -32,10 +31,16 @@ builder.Host.UseSerilog();
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
|
||||
// HttpContentAccessor
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
// Storage Singleton
|
||||
builder.Services.AddSingleton<IDashboardStore, DashboardStore>();
|
||||
|
||||
// Background Services
|
||||
builder.Services.AddHostedService<NetworkCheck>();
|
||||
builder.Services.AddHostedService<DatabaseCheck>();
|
||||
|
||||
|
||||
// ---------- Konfiguration ----------
|
||||
DotNetEnv.Env.Load();
|
||||
@@ -112,7 +117,11 @@ builder.Services.AddAuthentication()
|
||||
var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
|
||||
|
||||
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;
|
||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
||||
|
||||
var preferredUsername = principal.FindFirst("preferred_username")?.Value;
|
||||
var email = principal.FindFirst("email")?.Value;
|
||||
|
||||
@@ -213,4 +222,4 @@ app.MapControllerRoute(
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}"
|
||||
);
|
||||
|
||||
app.Run();
|
||||
app.Run();
|
8
Watcher/Services/DashboardStore.cs
Normal file
8
Watcher/Services/DashboardStore.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Watcher.Services;
|
||||
|
||||
public class DashboardStore : IDashboardStore
|
||||
{
|
||||
public String? NetworkStatus { get; set; }
|
||||
|
||||
public String? DatabaseStatus { get; set; }
|
||||
}
|
67
Watcher/Services/DatabaseCheck.cs
Normal file
67
Watcher/Services/DatabaseCheck.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace Watcher.Services;
|
||||
|
||||
public class DatabaseCheck : BackgroundService
|
||||
{
|
||||
private readonly ILogger<DatabaseCheck> _logger;
|
||||
|
||||
private IDashboardStore _dashboardStore;
|
||||
|
||||
public DatabaseCheck(ILogger<DatabaseCheck> logger, IDashboardStore dashboardStore)
|
||||
{
|
||||
_logger = logger;
|
||||
_dashboardStore = dashboardStore;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
|
||||
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
// Hintergrundprozess abwarten
|
||||
await checkDatabaseIntegrity();
|
||||
// 5 Sekdunden Offset
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
// String sqliteConnectionString als Argument übergeben
|
||||
public Task checkDatabaseIntegrity()
|
||||
{
|
||||
using var conn = new SqliteConnection("Data Source=./persistence/watcher.db");
|
||||
_logger.LogInformation("Sqlite Integrity-Check started...");
|
||||
|
||||
try
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
using var command = conn.CreateCommand();
|
||||
command.CommandText = """
|
||||
SELECT integrity_check FROM pragma_integrity_check;
|
||||
""";
|
||||
|
||||
using var reader = command.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
string status = reader.GetString(0);
|
||||
_dashboardStore.DatabaseStatus = status;
|
||||
_logger.LogInformation("Sqlite DatabaseIntegrity: ${status}", status);
|
||||
}
|
||||
|
||||
conn.Close();
|
||||
}
|
||||
catch (SqliteException e)
|
||||
{
|
||||
conn.Close();
|
||||
_logger.LogError(e.Message);
|
||||
|
||||
// TODO: LogEvent erstellen
|
||||
}
|
||||
|
||||
_logger.LogInformation("Database Integrity-Check finished.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
7
Watcher/Services/IDashboardStore.cs
Normal file
7
Watcher/Services/IDashboardStore.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Watcher.Services;
|
||||
|
||||
public interface IDashboardStore
|
||||
{
|
||||
String? NetworkStatus { get; set; }
|
||||
String? DatabaseStatus { get; set; }
|
||||
}
|
59
Watcher/Services/NetworkCheck.cs
Normal file
59
Watcher/Services/NetworkCheck.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Watcher.Services;
|
||||
public class NetworkCheck : BackgroundService
|
||||
{
|
||||
private readonly ILogger<NetworkCheck> _logger;
|
||||
|
||||
private IDashboardStore _DashboardStore;
|
||||
|
||||
public NetworkCheck(ILogger<NetworkCheck> logger, IDashboardStore DashboardStore)
|
||||
{
|
||||
_logger = logger;
|
||||
_DashboardStore = DashboardStore;
|
||||
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
|
||||
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
// Hintergrundprozess abwarten
|
||||
await checkConnectionAsync();
|
||||
|
||||
// 5 Sekdunden Offset
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
public Task checkConnectionAsync()
|
||||
{
|
||||
_logger.LogInformation("Networkcheck started.");
|
||||
|
||||
string host = "8.8.8.8";
|
||||
Ping p = new Ping();
|
||||
|
||||
try
|
||||
{
|
||||
PingReply reply = p.Send(host, 3000);
|
||||
if (reply.Status == IPStatus.Success)
|
||||
{
|
||||
_DashboardStore.NetworkStatus = "online";
|
||||
_logger.LogInformation("Ping successfull. Watcher is online.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_DashboardStore.NetworkStatus = "offline";
|
||||
_logger.LogError("Ping failed. Watcher appears to have no network connection.");
|
||||
|
||||
// LogEvent erstellen
|
||||
}
|
||||
|
||||
_logger.LogInformation("Networkcheck finished.");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
using Watcher.Models;
|
||||
|
||||
namespace Watcher.ViewModels
|
||||
{
|
||||
public class DashboardViewModel
|
||||
@@ -7,5 +9,13 @@ namespace Watcher.ViewModels
|
||||
public int RunningContainers { get; set; }
|
||||
public int FailedContainers { 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();
|
||||
|
||||
public String? NetworkStatus { get; set; } = "?";
|
||||
public String? DatabaseStatus { get; set; } = "?";
|
||||
|
||||
}
|
||||
}
|
||||
|
1
Watcher/Views/API/ApiReference.cshtml
Normal file
1
Watcher/Views/API/ApiReference.cshtml
Normal file
@@ -0,0 +1 @@
|
||||
<p>i</p>
|
@@ -5,55 +5,10 @@
|
||||
var oidc = ViewBag.oidc;
|
||||
}
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #0d1b2a;
|
||||
}
|
||||
|
||||
.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>
|
||||
<head>
|
||||
<link rel="stylesheet" href="~/css/main.css" />
|
||||
<link rel="stylesheet" href="~/css/Login.css" />
|
||||
</head>
|
||||
|
||||
<div class="login-card">
|
||||
<h2 class="text-center mb-4">Anmelden</h2>
|
||||
|
@@ -3,32 +3,37 @@
|
||||
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">
|
||||
@foreach (var server in Model.Servers)
|
||||
{
|
||||
<div class="bg-white shadow-md rounded-xl p-5 border border-gray-200 hover:shadow-lg transition duration-200">
|
||||
<h2 class="text-xl font-semibold mb-1">@server.Name</h2>
|
||||
</div>
|
||||
<div >
|
||||
<!-- TODO: Später auf > 0 ändern! -->
|
||||
<table class="ServiceList">
|
||||
<!-- foreach (var container in Model.Containers)-->
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>HostServer</th>
|
||||
<th>Status</th>
|
||||
<th>SSL/TLS</th>
|
||||
<th>Proxy</th>
|
||||
<th>Deployment</th>
|
||||
|
||||
<div class="bg-white">
|
||||
@if (Model.Containers.Count > 0)
|
||||
{
|
||||
<table>
|
||||
@foreach (var container in Model.Containers)
|
||||
{
|
||||
<tr>test</tr>
|
||||
if (container.HostServer.Equals(server.Name))
|
||||
{
|
||||
</tr>
|
||||
@for (int i = 0; i < 3; i++)
|
||||
{
|
||||
<tr class="ServiceRow">
|
||||
<td>test</td>
|
||||
<td><a class="ServiceEntry" href="/Server/Details/7">test</a></td>
|
||||
<td>Online</td>
|
||||
<td>true</td>
|
||||
<td>true</td>
|
||||
<td>Docker</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
}
|
||||
}
|
||||
</table>
|
||||
} else
|
||||
{
|
||||
<p> keine Container gefunden </p>
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
@@ -3,6 +3,11 @@
|
||||
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>
|
||||
|
||||
@if (TempData["Success"] != null)
|
||||
@@ -14,7 +19,7 @@
|
||||
<div class="alert alert-danger">@TempData["Error"]</div>
|
||||
}
|
||||
|
||||
<table class="table table-striped">
|
||||
<table class="dumptable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Dateiname</th>
|
||||
|
@@ -3,6 +3,9 @@
|
||||
ViewData["Title"] = "Dashboard";
|
||||
}
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
</head>
|
||||
<h1 class="mb-4">
|
||||
<i class="bi bi-speedometer2 me-2"></i>Dashboard
|
||||
</h1>
|
||||
@@ -11,25 +14,6 @@
|
||||
@await Html.PartialAsync("_DashboardStats", Model)
|
||||
</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 {
|
||||
<script>
|
||||
async function loadDashboardStats() {
|
||||
|
@@ -1,25 +1,184 @@
|
||||
@using Microsoft.IdentityModel.Tokens
|
||||
@model Watcher.ViewModels.DashboardViewModel
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<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>
|
||||
<p>🔴 Offline: <strong>@Model.OfflineServers</strong></p>
|
||||
<a href="/Server/Overview" class="text-primary text-decoration-none mt-2 d-inline-block">
|
||||
→ Zu den Servern
|
||||
</a>
|
||||
@{
|
||||
ViewData["Title"] = "Dashboard";
|
||||
}
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="~/css/main.css" />
|
||||
<link rel="stylesheet" href="~/css/dashboardstats.css" />
|
||||
</head>
|
||||
|
||||
<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 class="col-12 col-md-6">
|
||||
<div class="bg-white shadow rounded-3 p-4 h-100">
|
||||
<h2 class="h5 fw-semibold mb-2">Container</h2>
|
||||
<p>🟢 Laufend: <strong>@Model.RunningContainers</strong></p>
|
||||
<p>🔴 Fehlerhaft: <strong>@Model.FailedContainers</strong></p>
|
||||
<a href="/Container/Overview" class="text-primary text-decoration-none mt-2 d-inline-block">
|
||||
→ Zu den Containern
|
||||
</a>
|
||||
<!-- System Overview & Recent Events -->
|
||||
<div class="row g-4">
|
||||
<!-- System Health -->
|
||||
<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-heart-pulse me-2 text-danger"></i>Systemstatus</h5>
|
||||
|
||||
@if (!Model.NetworkStatus.IsNullOrEmpty())
|
||||
{
|
||||
@if (Model.NetworkStatus == "online")
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Netzwerk</span>
|
||||
<span class="badge bg-success">@Model.NetworkStatus</span>
|
||||
</div>
|
||||
<div class="progress mb-3" style="height: 6px;">
|
||||
<div class="progress-bar bg-success w-100"></div>
|
||||
</div>
|
||||
} else if (Model.NetworkStatus == "offline")
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Netzwerk</span>
|
||||
<span class="badge bg-danger">@Model.NetworkStatus</span>
|
||||
</div>
|
||||
<div class="progress mb-3" style="height: 6px;">
|
||||
<div class="progress-bar bg-danger w-100"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (!Model.DatabaseStatus.IsNullOrEmpty())
|
||||
{
|
||||
@if (Model.DatabaseStatus == "ok")
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Datenbank</span>
|
||||
<span class="badge bg-success">healthy</span>
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Datenbank</span>
|
||||
<span class="badge bg-danger">unhealthy</span>
|
||||
</div>
|
||||
}
|
||||
} else
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Datenbank</span>
|
||||
|
||||
<span class="badge bg-primary">Missing Data</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div class="progress mb-3" style="height: 6px;">
|
||||
<div class="progress-bar bg-success w-100"></div>
|
||||
</div>
|
||||
|
||||
</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 -->
|
||||
<!-- TODO
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card shadow rounded-3 p-4 h-100">
|
||||
<h2 class="h5 fw-semibold mb-3">Services</h2>
|
||||
<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 -->
|
||||
<!-- TODO
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card shadow rounded-3 p-4 h-100">
|
||||
<h2 class="h5 fw-semibold mb-3">Server</h2>
|
||||
<ul class="list-group list-group-flush">
|
||||
@foreach (Server server in Model.Servers)
|
||||
{
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center serverlist">
|
||||
<span>@server.Name</span>
|
||||
<span class="badge bg-info" )">
|
||||
CPU:
|
||||
</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>
|
@@ -3,6 +3,10 @@
|
||||
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="card shadow rounded-3 p-4">
|
||||
<h1 class="mb-4 text-primary-emphasis">
|
||||
|
@@ -3,76 +3,82 @@
|
||||
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 class="container mt-4"></div>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-hdd-network me-2 text-primary"></i>Serverdetails: @Model.Name
|
||||
</h5>
|
||||
<span class="badge @(Model.IsOnline ? "bg-success" : "bg-danger")">
|
||||
<i class="bi @(Model.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i>
|
||||
@(Model.IsOnline ? "Online" : "Offline")
|
||||
</span>
|
||||
</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>
|
||||
<div class="container mt-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-hdd-network me-2 text-primary"></i>@Model.Name
|
||||
</h5>
|
||||
<span class="badge @(Model.IsOnline ? "bg-success" : "bg-danger")">
|
||||
<i class="bi @(Model.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i>
|
||||
@(Model.IsOnline ? "Online" : "Offline")
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<dt class="col-sm-3">IP-Adresse</dt>
|
||||
<dd class="col-sm-9">@Model.IPAddress</dd>
|
||||
<div class="infocard row g-4 mb-4">
|
||||
<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>
|
||||
<dd class="col-sm-9">@Model.Type</dd>
|
||||
</div>
|
||||
|
||||
<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 class="mt-4">
|
||||
<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%">
|
||||
<canvas id="cpuUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas>
|
||||
<div class="graphcontainer p-4 text-center text-muted">
|
||||
<canvas class="graph" id="cpuUsageChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<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%">
|
||||
<canvas id="ramUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas>
|
||||
<div class="graphcontainer p-4 text-center text-muted">
|
||||
<canvas class="graph" id="ramUsageChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4"></div>
|
||||
<div class="mt-4">
|
||||
<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%">
|
||||
<canvas id="gpuUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas>
|
||||
<div class="graphcontainer p-4 text-center text-text">
|
||||
<canvas class="graph" id="gpuUsageChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
@@ -88,7 +94,6 @@
|
||||
datasets: [{
|
||||
label: 'CPU Last (%)',
|
||||
data: [],
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
@@ -109,7 +114,7 @@
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
display: false,
|
||||
text: 'Zeit'
|
||||
}
|
||||
}
|
||||
@@ -124,7 +129,6 @@
|
||||
datasets: [{
|
||||
label: 'RAM Last (%)',
|
||||
data: [],
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
@@ -140,12 +144,12 @@
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Prozent'
|
||||
text: 'Auslastung in %'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
display: false,
|
||||
text: 'Zeit'
|
||||
}
|
||||
}
|
||||
@@ -160,7 +164,6 @@
|
||||
datasets: [{
|
||||
label: 'GPU Last (%)',
|
||||
data: [],
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
@@ -176,12 +179,12 @@
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Prozent'
|
||||
text: 'Auslastung in %'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
display: false,
|
||||
text: 'Zeit'
|
||||
}
|
||||
}
|
||||
|
@@ -4,31 +4,36 @@
|
||||
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>
|
||||
|
||||
<form asp-action="EditServer" asp-controller="Server" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" asp-for="Id" />
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card mb-4 g-4">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">Allgemeine Informationen</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<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" />
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
<span asp-validation-for="IPAddress" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<option>VPS</option>
|
||||
<option>VM</option>
|
||||
@@ -176,6 +181,6 @@
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</form>
|
@@ -4,61 +4,45 @@
|
||||
<div class="row g-4">
|
||||
@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-body d-flex flex-column gap-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="card-title text-dark mb-0">
|
||||
<i class="bi bi-pc-display me-2 text-muted"></i>(#@s.Id) @s.Name
|
||||
<h5 class="card-title text-text mb-0">
|
||||
<i class="bi bi-pc-display me-2 text-text"></i>(#@s.Id) @s.Name
|
||||
</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>
|
||||
@(s.IsOnline ? "Online" : "Offline")
|
||||
</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 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>
|
||||
|
@@ -3,21 +3,31 @@
|
||||
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">
|
||||
<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>
|
||||
<form asp-action="AddServer" method="get" asp-controller="Server">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi"></i> neuen Server erstellen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a class="nav-link" href="/Server/addServer">
|
||||
<button class="btn btn-primary"> Server hnzufügen </button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="server-cards-container">
|
||||
@await Html.PartialAsync("_ServerCard", Model.Servers)
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
async function loadServerCards() {
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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 href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
width: 15rem;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background-color: #212121;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: #343a40;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -71,16 +71,22 @@
|
||||
<a class="nav-link" href="/">Dashboard</a>
|
||||
</li>
|
||||
<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>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/Container/Overview">Container</a>
|
||||
<a class="nav-link" href="/Container/Overview">Services</a>
|
||||
</li>
|
||||
<!-- Noch nicht implementiert
|
||||
<li class="nav-item"></li>
|
||||
<a class="nav-link" href="/Uptime/Overview">Uptime</a>
|
||||
</li>
|
||||
-->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/System/Settings">Einstellungen</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -92,6 +98,7 @@
|
||||
<div class="rounded-circle bg-secondary text-white px-2 py-1">
|
||||
<i class="bi bi-person"></i>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>@User.Identity?.Name</strong><br />
|
||||
<small class="text-muted">Profil ansehen</small>
|
||||
|
@@ -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;
|
||||
}
|
@@ -8,9 +8,11 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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" />
|
||||
</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="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
|
@@ -2,87 +2,47 @@
|
||||
ViewData["Title"] = "Settings";
|
||||
var pictureUrl = User.Claims.FirstOrDefault(c => c.Type == "picture")?.Value ?? "";
|
||||
var preferredUsername = User.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value ?? "admin";
|
||||
var isLocalUser = ViewBag.IdentityProvider == "local";
|
||||
var DbEngine = ViewBag.DbProvider;
|
||||
var mail = ViewBag.mail;
|
||||
var ServerVersion = ViewBag.ServerVersion;
|
||||
}
|
||||
|
||||
<style>
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
<head>
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
<link rel="stylesheet" href="~/css/settings.css" />
|
||||
</head>
|
||||
|
||||
<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;">
|
||||
<h4><i class="bi bi-pencil-square me-2"></i>Systemeinformationen</h4>
|
||||
|
||||
<br>
|
||||
|
||||
<h5>Watcher Version: v0.1.0</h5>
|
||||
<h5>Watcher Version: @ServerVersion</h5>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<h5>Authentifizierungsmethode: </h5>
|
||||
<p><strong>@(ViewBag.IdentityProvider ?? "nicht gefunden")</strong></p>
|
||||
<h5>Authentifizierungsmethode: <strong>@(ViewBag.IdentityProvider ?? "nicht gefunden")</strong></h5>
|
||||
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<h5>Datenbank-Engine: </h5>
|
||||
<strong>@(DbEngine ?? "nicht gefunden")</strong>
|
||||
<h5>Datenbank-Engine: <strong>@(DbEngine ?? "nicht gefunden")</strong></h5>
|
||||
|
||||
|
||||
<!-- Falls Sqlite verwendet wird können Backups erstellt werden -->
|
||||
@if (DbEngine == "Microsoft.EntityFrameworkCore.Sqlite")
|
||||
{
|
||||
<div class="d-flex gap-2">
|
||||
<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
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<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
|
||||
</button>
|
||||
</form>
|
||||
@@ -111,9 +71,8 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- action="/Notification/UpdateSettings" existiert noch nicht-->
|
||||
@@ -143,7 +102,7 @@
|
||||
<label class="form-check-label" for="notifyMaintenance">Wartungsinformationen erhalten</label>
|
||||
</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
|
||||
</button>
|
||||
</div>
|
@@ -4,14 +4,20 @@
|
||||
var Id = ViewBag.Id;
|
||||
var preferredUsername = ViewBag.name;
|
||||
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="card shadow-lg rounded-3 p-4" style="max-width: 700px; margin: auto;">
|
||||
<div class="text-center mb-4">
|
||||
@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
|
||||
{
|
||||
@@ -21,12 +27,12 @@
|
||||
</div>
|
||||
}
|
||||
<h3 class="mt-3">
|
||||
<i class="bi bi-person-circle me-1"></i>@preferredUsername
|
||||
<i class="bi"></i>@preferredUsername
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
|
||||
<table class="table table-hover">
|
||||
<table class="usertable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><i class="bi bi-person-badge me-1"></i>Username</th>
|
||||
@@ -34,11 +40,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<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>
|
||||
@if(IdProvider != "local")
|
||||
{
|
||||
@@ -83,12 +89,30 @@
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<form method="get" asp-controller="User" asp-action="UserSettings" class="text-center mt-4">
|
||||
<button type="submit" class="btn btn-info">
|
||||
<i class="bi bi-gear-wide-connected me-1"></i>Einstellungen
|
||||
|
||||
|
||||
<div class="card shadow mt-5 p-4" style="width: 100%; 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-db">
|
||||
<i class="bi bi-save me-1"></i>Speichern
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form method="post" asp-controller="Auth" asp-action="Logout" class="text-center mt-4">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-box-arrow-right me-1"></i>Abmelden
|
||||
|
24
Watcher/wwwroot/css/Login.css
Normal file
24
Watcher/wwwroot/css/Login.css
Normal 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;
|
||||
}
|
@@ -1 +0,0 @@
|
||||
|
4
Watcher/wwwroot/css/dashboardstats.css
Normal file
4
Watcher/wwwroot/css/dashboardstats.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.serverlist {
|
||||
background-color: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
13
Watcher/wwwroot/css/databases.css
Normal file
13
Watcher/wwwroot/css/databases.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.dumptable {
|
||||
width: 100%;
|
||||
|
||||
border: .2rem solid;
|
||||
|
||||
margin: 1rem;
|
||||
padding: 1rem
|
||||
}
|
||||
|
||||
.dumptable thead {
|
||||
margin: 1rem;
|
||||
border: .2rem solid;
|
||||
}
|
67
Watcher/wwwroot/css/main.css
Normal file
67
Watcher/wwwroot/css/main.css
Normal 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);
|
||||
}
|
20
Watcher/wwwroot/css/server-detail.css
Normal file
20
Watcher/wwwroot/css/server-detail.css
Normal 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;
|
||||
}
|
0
Watcher/wwwroot/css/server-edit.css
Normal file
0
Watcher/wwwroot/css/server-edit.css
Normal file
0
Watcher/wwwroot/css/server-overview.css
Normal file
0
Watcher/wwwroot/css/server-overview.css
Normal file
12
Watcher/wwwroot/css/services-overview.css
Normal file
12
Watcher/wwwroot/css/services-overview.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.ServiceList {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.ServiceRow {
|
||||
border-style: solid;
|
||||
border-color: var(--color-text);
|
||||
}
|
||||
|
||||
.ServiceEntry {
|
||||
text-decoration: none;
|
||||
}
|
24
Watcher/wwwroot/css/settings.css
Normal file
24
Watcher/wwwroot/css/settings.css
Normal 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;
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
:root {
|
||||
--color-bg: #0d1b2a;
|
||||
--color-surface: #1b263b;
|
||||
--color-bg: #141414;
|
||||
--color-surface: #212121;
|
||||
--color-accent: #415a77;
|
||||
--color-primary: #0d6efd;
|
||||
--color-text: #ffffff;
|
||||
--color-primary: white;
|
||||
--color-text: #f9feff;
|
||||
--color-muted: #c0c0c0;
|
||||
--color-success: #14a44d;
|
||||
--color-danger: #ff6b6b;
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
body {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -25,7 +25,7 @@ a {
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: var(--color-accent);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,6 @@ a {
|
||||
background-color: #0f8c3c;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid var(--color-accent);
|
||||
}
|
||||
|
39
Watcher/wwwroot/css/user-info.css
Normal file
39
Watcher/wwwroot/css/user-info.css
Normal 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;
|
||||
}
|
BIN
Watcher/wwwroot/img/watcher_logo.png
Normal file
BIN
Watcher/wwwroot/img/watcher_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
@@ -1,18 +1,16 @@
|
||||
services:
|
||||
watcher:
|
||||
image: git.triggermeelmo.com/daniel-hbn/watcher/watcher:development
|
||||
image: git.triggermeelmo.com/watcher/watcher-server:v0.1.0
|
||||
container_name: watcher
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 200M
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
ports:
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- ./data:/app/persistence
|
||||
- ./dumps:/app/wwwroot/downloads/sqlite
|
||||
- ./logs:/app/logs
|
||||
healthcheck:
|
||||
test: "curl -f http://localhost:5000"
|
||||
interval: 1m30s
|
||||
timeout: 30s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
- ./watcher-volumes/data:/app/persistence
|
||||
- ./watcher-volumes/dumps:/app/wwwroot/downloads/sqlite
|
||||
- ./watcher-volumes/logs:/app/logs
|
||||
|
Reference in New Issue
Block a user