diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..54ccced --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,147 @@ +name: Gitea CI/CD + +on: + workflow_dispatch: + push: + branches: [ "development", "main", "staging" ] + tags: [ "v*.*.*" ] + +env: + DOTNET_VERSION: '8.0.x' + DOCKER_IMAGE_NAME: watcher-server + REGISTRY_URL: git.triggermeelmo.com + DOCKER_PLATFORMS: 'linux/amd64,linux/arm64' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + dotnet-build-and-test: + runs-on: ubuntu-latest + 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 + needs: [dotnet-build-and-test] + #if: ${{ !failure() && !cancelled() && github.event_name != 'pull_request' }} + runs-on: ubuntu-latest + outputs: + tag_name: ${{ steps.set_tag.outputs.tag_name }} + should_tag: ${{ steps.set_tag.outputs.should_tag }} + 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 + new_tag="v${major}.${minor}.${patch}" + echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT + echo "should_tag=true" >> $GITHUB_OUTPUT + echo "Creating new major version tag: ${new_tag}" + + elif [[ "${GITHUB_REF}" == "refs/heads/development" ]]; then + minor=$((minor + 1)) + patch=0 + new_tag="v${major}.${minor}.${patch}" + echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT + echo "should_tag=true" >> $GITHUB_OUTPUT + echo "Creating new minor version tag: ${new_tag}" + + elif [[ "${GITHUB_REF}" == "refs/heads/staging" ]]; then + patch=$((patch + 1)) + new_tag="v${major}.${minor}.${patch}" + echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT + echo "should_tag=true" >> $GITHUB_OUTPUT + echo "Creating new patch version tag: ${new_tag}" + fi + + docker-build-and-push: + runs-on: ubuntu-latest + needs: [dotnet-build-and-test, set-tag] + if: | + needs.set-tag.outputs.should_tag == 'true' && + github.event_name != 'pull_request' + 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 }} \ + --build-arg VERSION=${{ needs.set-tag.outputs.tag_name }} \ + -t ${{ env.REGISTRY_URL }}/watcher/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.set-tag.outputs.tag_name }} \ + --push . + + tag: + name: Create Tag + needs: [docker-build-and-push, set-tag] + if: | + needs.set-tag.outputs.should_tag == 'true' && + github.event_name != 'pull_request' + runs-on: ubuntu-latest + 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: | + echo "Creating new tag: ${{ needs.set-tag.outputs.tag_name }}" + git tag ${{ needs.set-tag.outputs.tag_name }} + git push origin ${{ needs.set-tag.outputs.tag_name }} \ No newline at end of file diff --git a/.gitea/workflows/development-build.yaml b/.gitea/workflows/development-build.yaml deleted file mode 100644 index b3e7bac..0000000 --- a/.gitea/workflows/development-build.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Development Build - -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 - env: - RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache - 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 - env: - RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache - 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@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 }}/${{ env.DOCKER_IMAGE_NAME }}:development \ - -t ${{ env.REGISTRY_URL }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} \ - --push . diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml deleted file mode 100644 index bf599b5..0000000 --- a/.gitea/workflows/release.yaml +++ /dev/null @@ -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 . diff --git a/Dockerfile b/Dockerfile index a7adde3..0ef5a36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,10 @@ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false # 2. Laufzeit-Phase: ASP.NET Core Runtime FROM mcr.microsoft.com/dotnet/aspnet:8.0 + +# Build-Argument für Version (wird zur Build-Zeit vom CI/CD gesetzt) +ARG VERSION=latest + WORKDIR /app COPY --from=build /app/publish . @@ -28,5 +32,8 @@ EXPOSE 5000 ENV ASPNETCORE_URLS=http://*:5000 ENV ASPNETCORE_ENVIRONMENT=Development +# Version als Environment Variable setzen +ENV WATCHER_VERSION=${VERSION} + # Anwendung starten ENTRYPOINT ["dotnet", "Watcher.dll"] diff --git a/Tests/populate_testdata.py b/Tests/populate_testdata.py new file mode 100644 index 0000000..b87941c --- /dev/null +++ b/Tests/populate_testdata.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +""" +Watcher Database Test Data Population Script +Füllt die SQLite-Datenbank mit realistischen Testdaten für lokale Entwicklung +""" + +import sqlite3 +import random +from datetime import datetime, timedelta +import os + +# Datenbankpfad +DB_PATH = "Watcher\persistence\watcher.db" + +# Prüfe ob Datenbank existiert +if not os.path.exists(DB_PATH): + print(f"❌ Datenbank nicht gefunden: {DB_PATH}") + print("Bitte stelle sicher, dass die Anwendung einmal gestartet wurde, um die Datenbank zu erstellen.") + exit(1) + +print(f"📊 Verbinde mit Datenbank: {DB_PATH}") +conn = sqlite3.connect(DB_PATH) +cursor = conn.cursor() + +# Lösche vorhandene Daten (optional - auskommentieren wenn bestehende Daten behalten werden sollen) +print("\n🗑️ Lösche vorhandene Testdaten...") +cursor.execute("DELETE FROM ContainerMetrics") +cursor.execute("DELETE FROM Metrics") +cursor.execute("DELETE FROM LogEvents") +cursor.execute("DELETE FROM Containers") +cursor.execute("DELETE FROM Images") +cursor.execute("DELETE FROM Tags") +cursor.execute("DELETE FROM Servers") +# Users werden NICHT gelöscht +conn.commit() + +print("✅ Alte Daten gelöscht (Users bleiben erhalten)\n") + +# ============================================================================ +# 2. SERVERS - Test-Server erstellen +# ============================================================================ +print("\n🖥️ Erstelle Server...") + +servers_data = [ + { + "name": "Production-Web-01", + "ip": "192.168.1.10", + "type": "Ubuntu 22.04", + "description": "Haupt-Webserver für Production", + "cpu_type": "Intel Core i7-12700K", + "cpu_cores": 12, + "gpu_type": None, + "ram_size": 34359738368, # 32 GB in Bytes + "disk_space": "512 GB NVMe SSD", + "is_online": True + }, + { + "name": "Dev-Server", + "ip": "192.168.1.20", + "type": "Debian 12", + "description": "Entwicklungs- und Testserver", + "cpu_type": "AMD Ryzen 9 5900X", + "cpu_cores": 12, + "gpu_type": None, + "ram_size": 68719476736, # 64 GB in Bytes + "disk_space": "1 TB NVMe SSD", + "is_online": True + }, + { + "name": "GPU-Server-ML", + "ip": "192.168.1.30", + "type": "Ubuntu 22.04 LTS", + "description": "Machine Learning Training Server", + "cpu_type": "AMD Ryzen Threadripper 3970X", + "cpu_cores": 32, + "gpu_type": "NVIDIA RTX 4090", + "ram_size": 137438953472, # 128 GB in Bytes + "disk_space": "2 TB NVMe SSD", + "is_online": True + }, + { + "name": "Backup-Server", + "ip": "192.168.1.40", + "type": "Ubuntu 20.04", + "description": "Backup und Storage Server", + "cpu_type": "Intel Xeon E5-2680 v4", + "cpu_cores": 14, + "gpu_type": None, + "ram_size": 17179869184, # 16 GB in Bytes + "disk_space": "10 TB HDD RAID5", + "is_online": False + }, + { + "name": "Docker-Host-01", + "ip": "192.168.1.50", + "type": "Ubuntu 22.04", + "description": "Docker Container Host", + "cpu_type": "Intel Xeon Gold 6248R", + "cpu_cores": 24, + "gpu_type": None, + "ram_size": 68719476736, # 64 GB in Bytes + "disk_space": "2 TB NVMe SSD", + "is_online": True + } +] + +server_ids = [] +for server in servers_data: + last_seen = datetime.utcnow() - timedelta(minutes=random.randint(0, 30)) if server["is_online"] else datetime.utcnow() - timedelta(hours=random.randint(2, 48)) + + cursor.execute(""" + INSERT INTO Servers ( + Name, IPAddress, Type, Description, + CpuType, CpuCores, GpuType, RamSize, DiskSpace, + CPU_Load_Warning, CPU_Load_Critical, + CPU_Temp_Warning, CPU_Temp_Critical, + RAM_Load_Warning, RAM_Load_Critical, + GPU_Load_Warning, GPU_Load_Critical, + GPU_Temp_Warning, GPU_Temp_Critical, + Disk_Usage_Warning, Disk_Usage_Critical, + DISK_Temp_Warning, DISK_Temp_Critical, + CreatedAt, IsOnline, LastSeen, IsVerified + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + server["name"], server["ip"], server["type"], server["description"], + server["cpu_type"], server["cpu_cores"], server["gpu_type"], server["ram_size"], server["disk_space"], + 75.0, 90.0, # CPU Load + 80.0, 90.0, # CPU Temp + 85.0, 95.0, # RAM Load + 75.0, 90.0, # GPU Load + 70.0, 80.0, # GPU Temp + 75.0, 90.0, # Disk Usage + 34.0, 36.0, # Disk Temp + datetime.utcnow() - timedelta(days=random.randint(30, 365)), + server["is_online"], last_seen, True + )) + server_ids.append(cursor.lastrowid) + print(f" ✓ Server '{server['name']}' erstellt (ID: {cursor.lastrowid})") + +conn.commit() + +# ============================================================================ +# 3. METRICS - Server-Metriken erstellen (letzte 48 Stunden) +# ============================================================================ +print("\n📈 Erstelle Server-Metriken (letzte 48 Stunden)...") + +metrics_count = 0 +for server_id in server_ids: + # Finde den Server + cursor.execute("SELECT IsOnline FROM Servers WHERE Id = ?", (server_id,)) + is_online = cursor.fetchone()[0] + + if not is_online: + continue # Keine Metriken für offline Server + + # Erstelle Metriken für die letzten 48 Stunden (alle 5 Minuten) + start_time = datetime.utcnow() - timedelta(hours=48) + current_time = start_time + + # Basis-Werte für realistische Schwankungen + base_cpu = random.uniform(20, 40) + base_ram = random.uniform(40, 60) + base_gpu = random.uniform(10, 30) if server_id == server_ids[2] else 0 # Nur GPU-Server + + while current_time <= datetime.utcnow(): + # Realistische Schwankungen + cpu_load = max(0, min(100, base_cpu + random.gauss(0, 15))) + cpu_temp = 30 + (cpu_load * 0.5) + random.gauss(0, 3) + + ram_load = max(0, min(100, base_ram + random.gauss(0, 10))) + + gpu_load = max(0, min(100, base_gpu + random.gauss(0, 20))) if base_gpu > 0 else 0 + gpu_temp = 25 + (gpu_load * 0.6) + random.gauss(0, 3) if gpu_load > 0 else 0 + gpu_vram_usage = gpu_load * 0.8 if gpu_load > 0 else 0 + + disk_usage = random.uniform(40, 75) + disk_temp = random.uniform(28, 35) + + net_in = random.uniform(1000000, 10000000) # 1-10 Mbps in Bits + net_out = random.uniform(500000, 5000000) # 0.5-5 Mbps in Bits + + cursor.execute(""" + INSERT INTO Metrics ( + ServerId, Timestamp, + CPU_Load, CPU_Temp, + GPU_Load, GPU_Temp, GPU_Vram_Size, GPU_Vram_Usage, + RAM_Size, RAM_Load, + DISK_Size, DISK_Usage, DISK_Temp, + NET_In, NET_Out + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + server_id, current_time, + cpu_load, cpu_temp, + gpu_load, gpu_temp, 24.0, gpu_vram_usage, # 24 GB VRAM + 64.0, ram_load, + 512.0, disk_usage, disk_temp, + net_in, net_out + )) + + metrics_count += 1 + current_time += timedelta(minutes=5) + + print(f" ✓ Server {server_id}: {metrics_count} Metriken erstellt") + +conn.commit() +print(f"✅ Insgesamt {metrics_count} Metriken erstellt") + +# ============================================================================ +# 4. IMAGES - Docker Images +# ============================================================================ +print("\n🐳 Erstelle Docker Images...") + +images_data = [ + ("nginx", "latest"), + ("nginx", "alpine"), + ("postgres", "15"), + ("postgres", "14-alpine"), + ("redis", "7-alpine"), + ("node", "18-alpine"), + ("python", "3.11-slim"), + ("mysql", "8.0"), + ("traefik", "v2.10"), + ("portainer", "latest") +] + +image_ids = {} +for name, tag in images_data: + cursor.execute(""" + INSERT INTO Images (Name, Tag) + VALUES (?, ?) + """, (name, tag)) + image_ids[f"{name}:{tag}"] = cursor.lastrowid + print(f" ✓ Image '{name}:{tag}' erstellt") + +conn.commit() + +# ============================================================================ +# 5. CONTAINERS - Docker Container +# ============================================================================ +print("\n📦 Erstelle Docker Container...") + +# Nur für Server, die online sind +online_server_ids = [sid for sid in server_ids[:2]] # Erste 2 Server haben Container + +containers_data = [ + # Production-Web-01 + ("nginx-web", "abc123def456", "nginx:latest", online_server_ids[0], True), + ("postgres-db", "def456ghi789", "postgres:15", online_server_ids[0], True), + ("redis-cache", "ghi789jkl012", "redis:7-alpine", online_server_ids[0], True), + ("traefik-proxy", "jkl012mno345", "traefik:v2.10", online_server_ids[0], True), + + # Dev-Server + ("dev-nginx", "mno345pqr678", "nginx:alpine", online_server_ids[1], True), + ("dev-postgres", "pqr678stu901", "postgres:14-alpine", online_server_ids[1], False), + ("dev-redis", "stu901vwx234", "redis:7-alpine", online_server_ids[1], True), + ("test-app", "vwx234yz567", "node:18-alpine", online_server_ids[1], True), + ("portainer", "yz567abc890", "portainer:latest", online_server_ids[1], True), +] + +container_ids = [] +for name, container_id, image, server_id, is_running in containers_data: + cursor.execute(""" + INSERT INTO Containers (Name, ContainerId, Image, ServerId, IsRunning) + VALUES (?, ?, ?, ?, ?) + """, (name, container_id, image, server_id, is_running)) + container_ids.append(cursor.lastrowid) + status = "🟢 Running" if is_running else "🔴 Stopped" + print(f" ✓ Container '{name}' erstellt - {status}") + +conn.commit() + +# ============================================================================ +# 6. CONTAINER METRICS - Container-Metriken (letzte 24 Stunden) +# ============================================================================ +print("\n📊 Erstelle Container-Metriken (letzte 24 Stunden)...") + +container_metrics_count = 0 +for container_id in container_ids: + # Prüfe ob Container läuft + cursor.execute("SELECT IsRunning FROM Containers WHERE Id = ?", (container_id,)) + is_running = cursor.fetchone()[0] + + if not is_running: + continue # Keine Metriken für gestoppte Container + + # Erstelle Metriken für die letzten 24 Stunden (alle 5 Minuten) + start_time = datetime.utcnow() - timedelta(hours=24) + current_time = start_time + + # Basis-Werte für Container (meist niedriger als Host) + base_cpu = random.uniform(5, 15) + base_ram = random.uniform(10, 30) + + while current_time <= datetime.utcnow(): + cpu_load = max(0, min(100, base_cpu + random.gauss(0, 8))) + cpu_temp = 30 + (cpu_load * 0.5) + random.gauss(0, 2) + + ram_load = max(0, min(100, base_ram + random.gauss(0, 5))) + ram_size = random.uniform(0.5, 4.0) # Container nutzen weniger RAM + + cursor.execute(""" + INSERT INTO ContainerMetrics ( + ContainerId, Timestamp, + CPU_Load, CPU_Temp, + RAM_Size, RAM_Load + ) VALUES (?, ?, ?, ?, ?, ?) + """, ( + container_id, current_time, + cpu_load, cpu_temp, + ram_size, ram_load + )) + + container_metrics_count += 1 + current_time += timedelta(minutes=5) + +conn.commit() +print(f"✅ Insgesamt {container_metrics_count} Container-Metriken erstellt") + +# ============================================================================ +# 7. LOG EVENTS - Log-Einträge erstellen +# ============================================================================ +print("\n📝 Erstelle Log Events...") + +log_messages = [ + ("Info", "Server erfolgreich gestartet", None, None), + ("Info", "Backup abgeschlossen", server_ids[3], None), + ("Warning", "CPU-Auslastung über 80%", server_ids[0], None), + ("Info", "Container gestartet", server_ids[0], container_ids[0]), + ("Error", "Datenbank-Verbindung fehlgeschlagen", server_ids[1], container_ids[5]), + ("Warning", "Speicherplatz unter 25%", server_ids[1], None), + ("Info", "Update installiert", server_ids[2], None), + ("Info", "Container neu gestartet", server_ids[0], container_ids[2]), + ("Warning", "GPU-Temperatur über 75°C", server_ids[2], None), + ("Info", "Netzwerk-Check erfolgreich", server_ids[0], None), +] + +for level, message, server_id, container_id in log_messages: + timestamp = datetime.utcnow() - timedelta(hours=random.randint(0, 48)) + cursor.execute(""" + INSERT INTO LogEvents (Timestamp, Message, Level, ServerId, ContainerId) + VALUES (?, ?, ?, ?, ?) + """, (timestamp, message, level, server_id, container_id)) + print(f" ✓ Log: [{level}] {message}") + +conn.commit() + +# ============================================================================ +# 8. TAGS - Tags für Server/Container +# ============================================================================ +print("\n🏷️ Erstelle Tags...") + +tags_data = ["production", "development", "backup", "docker", "monitoring", "critical"] + +for tag_name in tags_data: + cursor.execute(""" + INSERT INTO Tags (Name) + VALUES (?) + """, (tag_name,)) + print(f" ✓ Tag '{tag_name}' erstellt") + +conn.commit() + +# ============================================================================ +# Abschluss +# ============================================================================ +print("\n" + "="*60) +print("✅ Testdaten erfolgreich erstellt!") +print("="*60) + +# Statistiken ausgeben +cursor.execute("SELECT COUNT(*) FROM Servers") +server_count = cursor.fetchone()[0] + +cursor.execute("SELECT COUNT(*) FROM Containers") +container_count = cursor.fetchone()[0] + +cursor.execute("SELECT COUNT(*) FROM Metrics") +metrics_count = cursor.fetchone()[0] + +cursor.execute("SELECT COUNT(*) FROM ContainerMetrics") +container_metrics_count = cursor.fetchone()[0] + +cursor.execute("SELECT COUNT(*) FROM LogEvents") +log_count = cursor.fetchone()[0] + +cursor.execute("SELECT COUNT(*) FROM Images") +image_count = cursor.fetchone()[0] + +cursor.execute("SELECT COUNT(*) FROM Tags") +tag_count = cursor.fetchone()[0] + +print(f""" +📊 STATISTIK: + 🖥️ Server: {server_count} + 📦 Container: {container_count} + 📈 Server-Metriken: {metrics_count} + 📊 Container-Metriken: {container_metrics_count} + 📝 Log Events: {log_count} + 🐳 Images: {image_count} + 🏷️ Tags: {tag_count} + +💡 HINWEIS: + - User-Tabelle wurde nicht verändert + - Metriken wurden für die letzten 48 Stunden generiert + - Server 'Backup-Server' ist offline (für Tests) + - Container 'dev-postgres' ist gestoppt (für Tests) + - Die Datenbank befindet sich unter: {DB_PATH} +""") + +# Verbindung schließen +conn.close() +print("🔒 Datenbankverbindung geschlossen\n") diff --git a/Tests/servicediscovery.py b/Tests/servicediscovery.py new file mode 100644 index 0000000..c114350 --- /dev/null +++ b/Tests/servicediscovery.py @@ -0,0 +1,57 @@ +import json +import urllib.request + +url = "http://localhost:5000/monitoring/service-discovery" +payload = { + "Server_id": 2, + "Containers": [ + { + "id": "3e74abf5ce30", + "image": "hello-world:latest", + "name": "serene_nightingale" + }, + { + "id": "83cd9d461690", + "image": "postgres:latest", + "name": "distracted_feistel" + }, + { + "id": "b296c2ed1213", + "image": "postgres:latest", + "name": "mystifying_jackson" + }, + { + "id": "69568181d576", + "image": "hello-world:latest", + "name": "romantic_driscoll" + }, + { + "id": "67c37a2b1791", + "image": "hello-world:latest", + "name": "asdf" + }, + { + "id": "8f39bae1e316", + "image": "hello-world:latest", + "name": "distracted_mirzakhani" + } + ] +} + +data = json.dumps(payload).encode("utf-8") +req = urllib.request.Request( +url, +data=data, +headers={"Content-Type": "application/json"}, +method="POST" +) + +try: + with urllib.request.urlopen(req) as response: + resp_data = response.read().decode("utf-8") + print("Status Code:", response.status) + print("Response:", resp_data) +except Exception as e: + print("Fehler beim Senden der Request:", e) + + diff --git a/Watcher/.env.example b/Watcher/.env.example index 032d787..87810d8 100644 --- a/Watcher/.env.example +++ b/Watcher/.env.example @@ -1,8 +1,19 @@ +# Application Version +# Bei lokalem Development wird "development" angezeigt, im Docker-Container die Image-Version +WATCHER_VERSION=development -# OIDC Einstellungen -AUTHENTICATION__USELOCAL=true -AUTHENTICATION__POCKETID__ENABLED=false -AUTHENTICATION__POCKETID__AUTHORITY=https://id.domain.app -AUTHENTICATION__POCKETID__CLIENTID= -AUTHENTICATION__POCKETID__CLIENTSECRET= -AUTHENTICATION__POCKETID__CALLBACKPATH=/signin-oidc +# Update Check +# Überprüft täglich, ob eine neue Version verfügbar ist +UPDATE_CHECK_ENABLED=true +UPDATE_CHECK_INTERVAL_HOURS=24 +UPDATE_CHECK_REPOSITORY_URL=https://git.triggermeelmo.com/api/v1/repos/Watcher/watcher/releases/latest + +# Data Retention Policy +# Wie lange sollen Metriken gespeichert werden (in Tagen)? +METRIC_RETENTION_DAYS=30 + +# Wie oft soll der Cleanup-Prozess laufen (in Stunden)? +METRIC_CLEANUP_INTERVAL_HOURS=24 + +# Soll der Cleanup-Service aktiviert sein? +METRIC_CLEANUP_ENABLED=true diff --git a/Watcher/Controllers/AuthController.cs b/Watcher/Controllers/AuthController.cs index b24110d..464a7c7 100644 --- a/Watcher/Controllers/AuthController.cs +++ b/Watcher/Controllers/AuthController.cs @@ -1,35 +1,20 @@ -using System.Net.Mail; using System.Security.Claims; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Options; using Watcher.Data; using Watcher.ViewModels; namespace Watcher.Controllers; -public class AppSettings -{ - public Boolean oidc { get; set; } -} - public class AuthController : Controller { private readonly AppDbContext _context; - private readonly AppSettings _settings; - - // Logging einbinden private readonly ILogger _logger; - - public AuthController(AppDbContext context, IOptions options, ILogger logger) + public AuthController(AppDbContext context, ILogger logger) { _context = context; - _settings = options.Value; _logger = logger; } @@ -43,7 +28,6 @@ public class AuthController : Controller ReturnUrl = returnUrl }; - ViewBag.oidc = _settings.oidc; return View(model); } @@ -88,32 +72,15 @@ public class AuthController : Controller } - // Login mit OIDC-Provider - public IActionResult SignIn() - { - return Challenge(new AuthenticationProperties - { - RedirectUri = "/Home/Index" - }, "oidc"); - } - - - // Logout [HttpPost] [ValidateAntiForgeryToken] public async Task Logout() { - var props = new AuthenticationProperties - { - RedirectUri = Url.Action("Login", "Auth") - }; - await HttpContext.SignOutAsync("Cookies"); - await HttpContext.SignOutAsync("oidc", props); _logger.LogInformation("User abgemeldet"); - return Redirect("/"); // nur als Fallback + return RedirectToAction("Login", "Auth"); } } diff --git a/Watcher/Controllers/ContainerController.cs b/Watcher/Controllers/ContainerController.cs index b18e4dd..5b3ee5b 100644 --- a/Watcher/Controllers/ContainerController.cs +++ b/Watcher/Controllers/ContainerController.cs @@ -18,17 +18,23 @@ public class ContainerController : Controller } public async Task Overview() -{ - var containers = await _context.Containers.ToListAsync(); + { + // Container mit Server-Informationen laden und nach Server sortieren + var containers = await _context.Containers + .Include(c => c.Server) + .OrderBy(c => c.Server.Name) + .ThenBy(c => c.Name) + .ToListAsync(); + var servers = await _context.Servers.ToListAsync(); - var viewModel = new ContainerOverviewViewModel - { - Servers = servers, - Containers = containers - }; + var viewModel = new ContainerOverviewViewModel + { + Servers = servers, + Containers = containers + }; - return View(viewModel); -} + return View(viewModel); + } } diff --git a/Watcher/Controllers/MonitoringController.cs b/Watcher/Controllers/MonitoringController.cs index 5ab60ff..faf6df9 100644 --- a/Watcher/Controllers/MonitoringController.cs +++ b/Watcher/Controllers/MonitoringController.cs @@ -1,17 +1,21 @@ using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; using Watcher.Data; using Watcher.Models; using Watcher.ViewModels; namespace Watcher.Controllers; -public class RegistrationDto +public class HardwareDto { // Server Identity [Required] @@ -77,6 +81,16 @@ public class MetricDto public double NET_Out { get; set; } // Bytes/s } +public class DockerServiceDto +{ + public required int Server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts + public required JsonElement Containers { get; set; } +} + +public class DockerServiceMetricDto +{ + +} [ApiController] [Route("[controller]")] @@ -93,9 +107,9 @@ public class MonitoringController : Controller } - // Endpoint, an dem sich neue Agents registrieren - [HttpPost("register-agent-by-id")] - public async Task Register([FromBody] RegistrationDto dto) + // Endpoint, an den der Agent seine Hardwareinformationen schickt + [HttpPost("hardware-info")] + public async Task Register([FromBody] HardwareDto dto) { // Gültigkeit des Payloads prüfen if (!ModelState.IsValid) @@ -128,13 +142,12 @@ public class MonitoringController : Controller _logger.LogInformation("Agent für '{server}' erfolgreich registriert.", server.Name); return Ok(); } - _logger.LogError("Kein Server für Registrierung gefunden"); return NotFound("No Matching Server found."); - } - [HttpGet("server-id-by-ip")] + // Endpoint, an dem sich ein Agent initial registriert + [HttpGet("register")] public async Task GetServerIdByIp([FromQuery] string IpAddress) { var server = await _context.Servers @@ -215,9 +228,140 @@ public class MonitoringController : Controller } + // Endpoint, an dem Agents Ihre laufenden Services registrieren + [HttpPost("service-discovery")] + public async Task ServiceDetection([FromBody] DockerServiceDto dto) + { + // Gültigkeit des Payloads prüfen + if (!ModelState.IsValid) + { + var errors = ModelState.Values + .SelectMany(v => v.Errors) + .Select(e => e.ErrorMessage) + .ToList(); + + _logger.LogError("Invalid ServiceDetection-Payload."); + return BadRequest(new { error = "Invalid Payload", details = errors }); + } + + // Prüfen, ob der Server existiert + var serverExists = await _context.Servers.AnyAsync(s => s.Id == dto.Server_id); + if (!serverExists) + { + _logger.LogError($"Server with ID {dto.Server_id} does not exist."); + return BadRequest(new { error = "Server not found", details = $"Server with ID {dto.Server_id} does not exist. Please register the server first." }); + } + + List newContainers = + JsonSerializer.Deserialize>(dto.Containers.GetRawText()) + ?? new List(); + + foreach (Container c in newContainers) + { + c.ServerId = dto.Server_id; + // Debug Logs + // TODO entfernen wenn fertig getestet + Console.WriteLine("---------"); + Console.WriteLine("ServerId: " + c.ServerId); + Console.WriteLine("ContainerId: " + c.ContainerId); + Console.WriteLine("Name: " + c.Name); + Console.WriteLine("Image: " + c.Image); + Console.WriteLine("---------"); + + } + + // Liste aller Container, die bereits der übergebenen ServerId zugewiesen sind + List existingContainers = _context.Containers + .Where(c => c.ServerId == dto.Server_id) + .ToList(); + + + // Logik, um Container, die mit dem Payload kamen zu verarbeiten + foreach (Container c in newContainers) + { + // Überprüfen, ob ein übergebener Container bereits für den Host registriert ist + if (existingContainers.Contains(c)) + { + _logger.LogInformation("Container with id " + c.ContainerId + " already exists."); + } + // Container auf einen Host/Server registrieren + else + { + // Container in Datenbank einlesen + try + { + _context.Containers.Add(c); + await _context.SaveChangesAsync(); + _logger.LogInformation(c.Name + " added for Host " + c.ServerId); + } + catch (SqliteException e) + { + _logger.LogError("Error writing new Containers to Database: " + e.Message); + } + } + } + + // Logik um abgeschaltene Container aus der Datenbank zu entfernen + foreach (Container c in existingContainers) + { + // Abfrage, ob bereits vorhandener Container im Payload vorhanden war + if (!newContainers.Contains(c)) + { + // Container entfernen + _context.Containers.Remove(c); + await _context.SaveChangesAsync(); + + // Metrics für den Container entfernen + //Todo + + _logger.LogInformation("Container " + c.Name + " (" + c.Id + ") on Host-Id " + c.ServerId + " was successfully removed from the database."); + } + } + + return Ok(); + + } + + // Endpoint, an den der Agent die Metrics der registrierten Container schickt + public async Task ServiceMetrics([FromBody] DockerServiceMetricDto dto) + { + // Gültigkeit des Payloads prüfen + if (!ModelState.IsValid) + { + var errors = ModelState.Values + .SelectMany(v => v.Errors) + .Select(e => e.ErrorMessage) + .ToList(); + + _logger.LogError("Invalid ServiceDetection-Payload."); + return BadRequest(new { error = "Invalid Payload", details = errors }); + } + + // Liste an Metrics aus der dto erstellen + List metrics = new List(); + + // Metrics in die Datenbank eintragen + try + { + foreach (ContainerMetric m in metrics) + { + _context.ContainerMetrics.Add(m); + await _context.SaveChangesAsync(); + // _logger.LogInformation(m. + " added for Host " + c.ServerId); + return Ok(); + } + } + catch (SqliteException e) + { + _logger.LogError(e.Message); + return StatusCode(500); + } + + return Ok(); + + } // Durchschnittliche Werte Berechnen - [HttpGet("median")] public async Task CalculateMedian(string Metric, int HoursToMonitor, int ServerId) { // Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll @@ -234,53 +378,62 @@ public class MonitoringController : Controller } [HttpGet("cpu-usage")] - public async Task GetCpuUsageData(int serverId) + public async Task GetCpuUsageData(int serverId, int hours = 1) { - var oneDayAgo = DateTime.UtcNow.AddDays(-1); - var data = await _context.Metrics - .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId) + var startTime = DateTime.UtcNow.AddHours(-hours); + var metrics = await _context.Metrics + .Where(m => m.Timestamp >= startTime && m.ServerId == serverId) .OrderBy(m => m.Timestamp) - .Select(m => new - { - label = m.Timestamp.ToUniversalTime().ToString("o"), - data = m.CPU_Load - }) .ToListAsync(); + // Timestamp-Format basierend auf Zeitbereich anpassen + string format = hours > 1 ? "dd.MM HH:mm" : "HH:mm"; + var data = metrics.Select(m => new + { + label = m.Timestamp.ToLocalTime().ToString(format), + data = m.CPU_Load + }).ToList(); + return Ok(data); } [HttpGet("ram-usage")] - public async Task GetRamUsageData(int serverId) + public async Task GetRamUsageData(int serverId, int hours = 1) { - var oneDayAgo = DateTime.UtcNow.AddDays(-1); - var data = await _context.Metrics - .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId) + var startTime = DateTime.UtcNow.AddHours(-hours); + var metrics = await _context.Metrics + .Where(m => m.Timestamp >= startTime && m.ServerId == serverId) .OrderBy(m => m.Timestamp) - .Select(m => new - { - label = m.Timestamp.ToUniversalTime().ToString("o"), - data = m.RAM_Load - }) .ToListAsync(); + // Timestamp-Format basierend auf Zeitbereich anpassen + string format = hours > 1 ? "dd.MM HH:mm" : "HH:mm"; + var data = metrics.Select(m => new + { + label = m.Timestamp.ToLocalTime().ToString(format), + data = m.RAM_Load + }).ToList(); + return Ok(data); } [HttpGet("gpu-usage")] - public async Task GetGpuUsageData(int serverId) + public async Task GetGpuUsageData(int serverId, int hours = 1) { - var oneDayAgo = DateTime.UtcNow.AddDays(-1); - var data = await _context.Metrics - .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId) + var startTime = DateTime.UtcNow.AddHours(-hours); + var metrics = await _context.Metrics + .Where(m => m.Timestamp >= startTime && m.ServerId == serverId) .OrderBy(m => m.Timestamp) - .Select(m => new - { - label = m.Timestamp.ToUniversalTime().ToString("o"), - data = m.GPU_Load - }) .ToListAsync(); + // Timestamp-Format basierend auf Zeitbereich anpassen + string format = hours > 1 ? "dd.MM HH:mm" : "HH:mm"; + var data = metrics.Select(m => new + { + label = m.Timestamp.ToLocalTime().ToString(format), + data = m.GPU_Load + }).ToList(); + return Ok(data); } @@ -317,6 +470,13 @@ public class MonitoringController : Controller return metric_input; } + private List ParseServiceDiscoveryInput(int server_id, List containers) + { + List containerList = new List(); + + // JSON-Objekt auslesen und Container-Objekte erstellen + return containerList; + } } \ No newline at end of file diff --git a/Watcher/Controllers/SystemController.cs b/Watcher/Controllers/SystemController.cs index e39ff2c..0190055 100644 --- a/Watcher/Controllers/SystemController.cs +++ b/Watcher/Controllers/SystemController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Watcher.Data; +using Watcher.Services; using Watcher.ViewModels; namespace Watcher.Controllers; @@ -11,11 +12,13 @@ public class SystemController : Controller { private readonly AppDbContext _context; private readonly ILogger _logger; + private readonly IVersionService _versionService; - public SystemController(AppDbContext context, ILogger logger) + public SystemController(AppDbContext context, ILogger logger, IVersionService versionService) { _context = context; _logger = logger; + _versionService = versionService; } // Edit-Form anzeigen @@ -23,10 +26,32 @@ public class SystemController : Controller //public async Task Settings() public IActionResult Settings() { - ViewBag.DbProvider = "Microsoft.EntityFrameworkCore.Sqlite"; + ViewBag.DbProvider = "SQLite"; ViewBag.mail = "test@mail.com"; ViewBag.IdentityProvider = "Local"; - ViewBag.ServerVersion = "v0.1.0"; + ViewBag.ServerVersion = _versionService.GetVersion(); + + // Datenbankgröße ermitteln + try + { + var dbPath = "./persistence/watcher.db"; + if (System.IO.File.Exists(dbPath)) + { + var fileInfo = new System.IO.FileInfo(dbPath); + var sizeInMiB = fileInfo.Length / (1024.0 * 1024.0); + ViewBag.DatabaseSize = $"{sizeInMiB:F2} MiB"; + } + else + { + ViewBag.DatabaseSize = "Nicht gefunden"; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Fehler beim Ermitteln der Datenbankgröße."); + ViewBag.DatabaseSize = "Fehler beim Laden"; + } + return View(); } diff --git a/Watcher/Controllers/UserController.cs b/Watcher/Controllers/UserController.cs index b227618..b4ef71e 100644 --- a/Watcher/Controllers/UserController.cs +++ b/Watcher/Controllers/UserController.cs @@ -33,14 +33,12 @@ public class UserController : Controller var username = user.Username; var mail = user.Email; var Id = user.Id; - var IdProvider = user.IdentityProvider; // Anzeigedaten an View übergeben ViewBag.Claims = claims; ViewBag.Name = username; ViewBag.Mail = mail; ViewBag.Id = Id; - ViewBag.IdProvider = IdProvider; return View(); diff --git a/Watcher/Data/AppDbContext.cs b/Watcher/Data/AppDbContext.cs index c88df1b..36030f1 100644 --- a/Watcher/Data/AppDbContext.cs +++ b/Watcher/Data/AppDbContext.cs @@ -26,30 +26,19 @@ public class AppDbContext : DbContext public DbSet Tags { get; set; } + public DbSet ContainerMetrics { get; set; } + public DbSet Users { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { - var provider = _configuration["Database:Provider"]; - - if (provider == "MySql") - { - var connStr = _configuration.GetConnectionString("MySql") - ?? _configuration["Database:ConnectionStrings:MySql"]; - optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr)); - } - else if (provider == "Sqlite") - { - var connStr = _configuration.GetConnectionString("Sqlite") - ?? _configuration["Database:ConnectionStrings:Sqlite"]; - optionsBuilder.UseSqlite(connStr); - } - else - { - throw new Exception("Unsupported database provider configured."); - } + // Nur SQLite wird unterstützt + var connStr = _configuration.GetConnectionString("Sqlite") + ?? _configuration["Database:ConnectionStrings:Sqlite"] + ?? "Data Source=./persistence/watcher.db"; + optionsBuilder.UseSqlite(connStr); } } } diff --git a/Watcher/Migrations/20250617153602_InitialMigration.Designer.cs b/Watcher/Migrations/20250617153602_InitialMigration.Designer.cs deleted file mode 100644 index 5d549f0..0000000 --- a/Watcher/Migrations/20250617153602_InitialMigration.Designer.cs +++ /dev/null @@ -1,300 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Watcher.Data; - -#nullable disable - -namespace Watcher.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20250617153602_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("CreatedAt") - .HasColumnType("datetime(6)"); - - b.Property("Hostname") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ImageId") - .HasColumnType("int"); - - b.Property("IsRunning") - .HasColumnType("tinyint(1)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Status") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("TagId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.HasIndex("TagId"); - - b.ToTable("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("Tag") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.ToTable("Images"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("ContainerId") - .HasColumnType("int"); - - b.Property("Level") - .HasColumnType("longtext"); - - b.Property("Message") - .HasColumnType("longtext"); - - b.Property("ServerId") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("LogEvents"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("ContainerId") - .HasColumnType("int"); - - b.Property("ServerId") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("longtext"); - - b.Property("Value") - .HasColumnType("double"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("Metrics"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("CpuCores") - .HasColumnType("int"); - - b.Property("CpuType") - .HasColumnType("longtext"); - - b.Property("CreatedAt") - .HasColumnType("datetime(6)"); - - b.Property("Description") - .HasColumnType("longtext"); - - b.Property("GpuType") - .HasColumnType("longtext"); - - b.Property("IPAddress") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("IsOnline") - .HasColumnType("tinyint(1)"); - - b.Property("LastSeen") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("RamSize") - .HasColumnType("double"); - - b.Property("TagId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("TagId"); - - b.ToTable("Servers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Watcher.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Email") - .HasColumnType("longtext"); - - b.Property("LastLogin") - .HasColumnType("datetime(6)"); - - b.Property("PocketId") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("PreferredUsername") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.HasOne("Watcher.Models.Image", "Image") - .WithMany("Containers") - .HasForeignKey("ImageId"); - - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Containers") - .HasForeignKey("TagId"); - - b.Navigation("Image"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Servers") - .HasForeignKey("TagId"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Navigation("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Navigation("Containers"); - - b.Navigation("Servers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Watcher/Migrations/20250617153602_InitialMigration.cs b/Watcher/Migrations/20250617153602_InitialMigration.cs deleted file mode 100644 index e8bc8fd..0000000 --- a/Watcher/Migrations/20250617153602_InitialMigration.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class InitialMigration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Images", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - Tag = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4") - }, - constraints: table => - { - table.PrimaryKey("PK_Images", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Tags", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4") - }, - constraints: table => - { - table.PrimaryKey("PK_Tags", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - PocketId = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - PreferredUsername = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Email = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - LastLogin = table.Column(type: "datetime(6)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Containers", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Status = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - ImageId = table.Column(type: "int", nullable: true), - CreatedAt = table.Column(type: "datetime(6)", nullable: false), - Hostname = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Type = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - IsRunning = table.Column(type: "tinyint(1)", nullable: false), - TagId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Containers", x => x.Id); - table.ForeignKey( - name: "FK_Containers_Images_ImageId", - column: x => x.ImageId, - principalTable: "Images", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_Containers_Tags_TagId", - column: x => x.TagId, - principalTable: "Tags", - principalColumn: "Id"); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Servers", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - IPAddress = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - CreatedAt = table.Column(type: "datetime(6)", nullable: false), - Type = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - IsOnline = table.Column(type: "tinyint(1)", nullable: false), - LastSeen = table.Column(type: "datetime(6)", nullable: false), - Description = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - CpuType = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - CpuCores = table.Column(type: "int", nullable: false), - GpuType = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - RamSize = table.Column(type: "double", nullable: false), - TagId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Servers", x => x.Id); - table.ForeignKey( - name: "FK_Servers_Tags_TagId", - column: x => x.TagId, - principalTable: "Tags", - principalColumn: "Id"); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "LogEvents", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Timestamp = table.Column(type: "datetime(6)", nullable: false), - Message = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - Level = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - ServerId = table.Column(type: "int", nullable: true), - ContainerId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_LogEvents", x => x.Id); - table.ForeignKey( - name: "FK_LogEvents_Containers_ContainerId", - column: x => x.ContainerId, - principalTable: "Containers", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_LogEvents_Servers_ServerId", - column: x => x.ServerId, - principalTable: "Servers", - principalColumn: "Id"); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Metrics", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Timestamp = table.Column(type: "datetime(6)", nullable: false), - Type = table.Column(type: "longtext", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - Value = table.Column(type: "double", nullable: false), - ServerId = table.Column(type: "int", nullable: true), - ContainerId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Metrics", x => x.Id); - table.ForeignKey( - name: "FK_Metrics_Containers_ContainerId", - column: x => x.ContainerId, - principalTable: "Containers", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_Metrics_Servers_ServerId", - column: x => x.ServerId, - principalTable: "Servers", - principalColumn: "Id"); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateIndex( - name: "IX_Containers_ImageId", - table: "Containers", - column: "ImageId"); - - migrationBuilder.CreateIndex( - name: "IX_Containers_TagId", - table: "Containers", - column: "TagId"); - - migrationBuilder.CreateIndex( - name: "IX_LogEvents_ContainerId", - table: "LogEvents", - column: "ContainerId"); - - migrationBuilder.CreateIndex( - name: "IX_LogEvents_ServerId", - table: "LogEvents", - column: "ServerId"); - - migrationBuilder.CreateIndex( - name: "IX_Metrics_ContainerId", - table: "Metrics", - column: "ContainerId"); - - migrationBuilder.CreateIndex( - name: "IX_Metrics_ServerId", - table: "Metrics", - column: "ServerId"); - - migrationBuilder.CreateIndex( - name: "IX_Servers_TagId", - table: "Servers", - column: "TagId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "LogEvents"); - - migrationBuilder.DropTable( - name: "Metrics"); - - migrationBuilder.DropTable( - name: "Users"); - - migrationBuilder.DropTable( - name: "Containers"); - - migrationBuilder.DropTable( - name: "Servers"); - - migrationBuilder.DropTable( - name: "Images"); - - migrationBuilder.DropTable( - name: "Tags"); - } - } -} diff --git a/Watcher/Migrations/20250617165126_ServerPrimaryKey.Designer.cs b/Watcher/Migrations/20250617165126_ServerPrimaryKey.Designer.cs deleted file mode 100644 index 14cf440..0000000 --- a/Watcher/Migrations/20250617165126_ServerPrimaryKey.Designer.cs +++ /dev/null @@ -1,298 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Watcher.Data; - -#nullable disable - -namespace Watcher.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20250617165126_ServerPrimaryKey")] - partial class ServerPrimaryKey - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Hostname") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ImageId") - .HasColumnType("INTEGER"); - - b.Property("IsRunning") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.HasIndex("TagId"); - - b.ToTable("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Images"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("Level") - .HasColumnType("TEXT"); - - b.Property("Message") - .HasColumnType("TEXT"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("LogEvents"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("REAL"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("Metrics"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CpuCores") - .HasColumnType("INTEGER"); - - b.Property("CpuType") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnType("TEXT"); - - b.Property("GpuType") - .HasColumnType("TEXT"); - - b.Property("IPAddress") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("IsOnline") - .HasColumnType("INTEGER"); - - b.Property("LastSeen") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RamSize") - .HasColumnType("REAL"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TagId"); - - b.ToTable("Servers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Watcher.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("LastLogin") - .HasColumnType("TEXT"); - - b.Property("PocketId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PreferredUsername") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.HasOne("Watcher.Models.Image", "Image") - .WithMany("Containers") - .HasForeignKey("ImageId"); - - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Containers") - .HasForeignKey("TagId"); - - b.Navigation("Image"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Servers") - .HasForeignKey("TagId"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Navigation("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Navigation("Containers"); - - b.Navigation("Servers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Watcher/Migrations/20250617165126_ServerPrimaryKey.cs b/Watcher/Migrations/20250617165126_ServerPrimaryKey.cs deleted file mode 100644 index f258b61..0000000 --- a/Watcher/Migrations/20250617165126_ServerPrimaryKey.cs +++ /dev/null @@ -1,785 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class ServerPrimaryKey : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PreferredUsername", - table: "Users", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "PocketId", - table: "Users", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "LastLogin", - table: "Users", - type: "TEXT", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "datetime(6)"); - - migrationBuilder.AlterColumn( - name: "Email", - table: "Users", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Users", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Tags", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Tags", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Type", - table: "Servers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "TagId", - table: "Servers", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "RamSize", - table: "Servers", - type: "REAL", - nullable: false, - oldClrType: typeof(double), - oldType: "double"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Servers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "LastSeen", - table: "Servers", - type: "TEXT", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "datetime(6)"); - - migrationBuilder.AlterColumn( - name: "IsOnline", - table: "Servers", - type: "INTEGER", - nullable: false, - oldClrType: typeof(bool), - oldType: "tinyint(1)"); - - migrationBuilder.AlterColumn( - name: "IPAddress", - table: "Servers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "GpuType", - table: "Servers", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Servers", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "Servers", - type: "TEXT", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "datetime(6)"); - - migrationBuilder.AlterColumn( - name: "CpuType", - table: "Servers", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "CpuCores", - table: "Servers", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Servers", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Value", - table: "Metrics", - type: "REAL", - nullable: false, - oldClrType: typeof(double), - oldType: "double"); - - migrationBuilder.AlterColumn( - name: "Type", - table: "Metrics", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Timestamp", - table: "Metrics", - type: "TEXT", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "datetime(6)"); - - migrationBuilder.AlterColumn( - name: "ServerId", - table: "Metrics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ContainerId", - table: "Metrics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Metrics", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Timestamp", - table: "LogEvents", - type: "TEXT", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "datetime(6)"); - - migrationBuilder.AlterColumn( - name: "ServerId", - table: "LogEvents", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Message", - table: "LogEvents", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Level", - table: "LogEvents", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ContainerId", - table: "LogEvents", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "LogEvents", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Tag", - table: "Images", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Images", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "longtext", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Images", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Type", - table: "Containers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "TagId", - table: "Containers", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Status", - table: "Containers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Containers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "IsRunning", - table: "Containers", - type: "INTEGER", - nullable: false, - oldClrType: typeof(bool), - oldType: "tinyint(1)"); - - migrationBuilder.AlterColumn( - name: "ImageId", - table: "Containers", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Hostname", - table: "Containers", - type: "TEXT", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "Containers", - type: "TEXT", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "datetime(6)"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Containers", - type: "INTEGER", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PreferredUsername", - table: "Users", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "PocketId", - table: "Users", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "LastLogin", - table: "Users", - type: "datetime(6)", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "Email", - table: "Users", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Users", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Tags", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Tags", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Type", - table: "Servers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "TagId", - table: "Servers", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "RamSize", - table: "Servers", - type: "double", - nullable: false, - oldClrType: typeof(double), - oldType: "REAL"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Servers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "LastSeen", - table: "Servers", - type: "datetime(6)", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "IsOnline", - table: "Servers", - type: "tinyint(1)", - nullable: false, - oldClrType: typeof(bool), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "IPAddress", - table: "Servers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "GpuType", - table: "Servers", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Servers", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "Servers", - type: "datetime(6)", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "CpuType", - table: "Servers", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "CpuCores", - table: "Servers", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Servers", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Value", - table: "Metrics", - type: "double", - nullable: false, - oldClrType: typeof(double), - oldType: "REAL"); - - migrationBuilder.AlterColumn( - name: "Type", - table: "Metrics", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Timestamp", - table: "Metrics", - type: "datetime(6)", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "ServerId", - table: "Metrics", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ContainerId", - table: "Metrics", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Metrics", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Timestamp", - table: "LogEvents", - type: "datetime(6)", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "ServerId", - table: "LogEvents", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Message", - table: "LogEvents", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Level", - table: "LogEvents", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ContainerId", - table: "LogEvents", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "LogEvents", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Tag", - table: "Images", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Images", - type: "longtext", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Images", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - - migrationBuilder.AlterColumn( - name: "Type", - table: "Containers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "TagId", - table: "Containers", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Status", - table: "Containers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Containers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "IsRunning", - table: "Containers", - type: "tinyint(1)", - nullable: false, - oldClrType: typeof(bool), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "ImageId", - table: "Containers", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Hostname", - table: "Containers", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "Containers", - type: "datetime(6)", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Containers", - type: "int", - nullable: false, - oldClrType: typeof(int), - oldType: "INTEGER") - .Annotation("Sqlite:Autoincrement", true) - .OldAnnotation("Sqlite:Autoincrement", true); - } - } -} diff --git a/Watcher/Migrations/20250617174242_UserPasswordAdded.Designer.cs b/Watcher/Migrations/20250617174242_UserPasswordAdded.Designer.cs deleted file mode 100644 index 123e1fa..0000000 --- a/Watcher/Migrations/20250617174242_UserPasswordAdded.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Watcher.Data; - -#nullable disable - -namespace Watcher.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20250617174242_UserPasswordAdded")] - partial class UserPasswordAdded - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Hostname") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ImageId") - .HasColumnType("INTEGER"); - - b.Property("IsRunning") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.HasIndex("TagId"); - - b.ToTable("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Images"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("Level") - .HasColumnType("TEXT"); - - b.Property("Message") - .HasColumnType("TEXT"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("LogEvents"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("REAL"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("Metrics"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CpuCores") - .HasColumnType("INTEGER"); - - b.Property("CpuType") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnType("TEXT"); - - b.Property("GpuType") - .HasColumnType("TEXT"); - - b.Property("IPAddress") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("IsOnline") - .HasColumnType("INTEGER"); - - b.Property("LastSeen") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RamSize") - .HasColumnType("REAL"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TagId"); - - b.ToTable("Servers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Watcher.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LastLogin") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PocketId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PreferredUsername") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.HasOne("Watcher.Models.Image", "Image") - .WithMany("Containers") - .HasForeignKey("ImageId"); - - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Containers") - .HasForeignKey("TagId"); - - b.Navigation("Image"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Servers") - .HasForeignKey("TagId"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Navigation("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Navigation("Containers"); - - b.Navigation("Servers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Watcher/Migrations/20250617174242_UserPasswordAdded.cs b/Watcher/Migrations/20250617174242_UserPasswordAdded.cs deleted file mode 100644 index 04ef332..0000000 --- a/Watcher/Migrations/20250617174242_UserPasswordAdded.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class UserPasswordAdded : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "IdentityProvider", - table: "Users", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "Password", - table: "Users", - type: "TEXT", - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "IdentityProvider", - table: "Users"); - - migrationBuilder.DropColumn( - name: "Password", - table: "Users"); - } - } -} diff --git a/Watcher/Migrations/20250621124832_DB-Update Issue#32.Designer.cs b/Watcher/Migrations/20250621124832_DB-Update Issue#32.Designer.cs deleted file mode 100644 index e8cad36..0000000 --- a/Watcher/Migrations/20250621124832_DB-Update Issue#32.Designer.cs +++ /dev/null @@ -1,316 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Watcher.Data; - -#nullable disable - -namespace Watcher.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20250621124832_DB-Update Issue#32")] - partial class DBUpdateIssue32 - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Hostname") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ImageId") - .HasColumnType("INTEGER"); - - b.Property("IsRunning") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.HasIndex("TagId"); - - b.ToTable("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Images"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("Level") - .HasColumnType("TEXT"); - - b.Property("Message") - .HasColumnType("TEXT"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("LogEvents"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CPU_Load") - .HasColumnType("REAL"); - - b.Property("CPU_Temp") - .HasColumnType("REAL"); - - b.Property("DISK_Size") - .HasColumnType("REAL"); - - b.Property("DISK_Temp") - .HasColumnType("REAL"); - - b.Property("DISK_Usage") - .HasColumnType("REAL"); - - b.Property("GPU_Load") - .HasColumnType("REAL"); - - b.Property("GPU_Temp") - .HasColumnType("REAL"); - - b.Property("GPU_Vram_Size") - .HasColumnType("REAL"); - - b.Property("GPU_Vram_Usage") - .HasColumnType("REAL"); - - b.Property("NET_In") - .HasColumnType("REAL"); - - b.Property("NET_Out") - .HasColumnType("REAL"); - - b.Property("RAM_Load") - .HasColumnType("REAL"); - - b.Property("RAM_Size") - .HasColumnType("REAL"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Metrics"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CpuCores") - .HasColumnType("INTEGER"); - - b.Property("CpuType") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnType("TEXT"); - - b.Property("GpuType") - .HasColumnType("TEXT"); - - b.Property("IPAddress") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("IsOnline") - .HasColumnType("INTEGER"); - - b.Property("LastSeen") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RamSize") - .HasColumnType("REAL"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TagId"); - - b.ToTable("Servers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Watcher.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LastLogin") - .HasColumnType("TEXT"); - - b.Property("OIDC_Id") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.HasOne("Watcher.Models.Image", "Image") - .WithMany("Containers") - .HasForeignKey("ImageId"); - - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Containers") - .HasForeignKey("TagId"); - - b.Navigation("Image"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Servers") - .HasForeignKey("TagId"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Navigation("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Navigation("Containers"); - - b.Navigation("Servers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Watcher/Migrations/20250621124832_DB-Update Issue#32.cs b/Watcher/Migrations/20250621124832_DB-Update Issue#32.cs deleted file mode 100644 index b7644a8..0000000 --- a/Watcher/Migrations/20250621124832_DB-Update Issue#32.cs +++ /dev/null @@ -1,251 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class DBUpdateIssue32 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Metrics_Containers_ContainerId", - table: "Metrics"); - - migrationBuilder.DropForeignKey( - name: "FK_Metrics_Servers_ServerId", - table: "Metrics"); - - migrationBuilder.DropIndex( - name: "IX_Metrics_ContainerId", - table: "Metrics"); - - migrationBuilder.DropIndex( - name: "IX_Metrics_ServerId", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "PocketId", - table: "Users"); - - migrationBuilder.DropColumn( - name: "ContainerId", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "Type", - table: "Metrics"); - - migrationBuilder.RenameColumn( - name: "PreferredUsername", - table: "Users", - newName: "Username"); - - migrationBuilder.RenameColumn( - name: "Value", - table: "Metrics", - newName: "RAM_Size"); - - migrationBuilder.AddColumn( - name: "OIDC_Id", - table: "Users", - type: "TEXT", - nullable: true); - - migrationBuilder.AddColumn( - name: "CPU_Load", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "CPU_Temp", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "DISK_Size", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "DISK_Temp", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "DISK_Usage", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Load", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Temp", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Vram_Size", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Vram_Usage", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "NET_In", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "NET_Out", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "RAM_Load", - table: "Metrics", - type: "REAL", - nullable: false, - defaultValue: 0.0); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "OIDC_Id", - table: "Users"); - - migrationBuilder.DropColumn( - name: "CPU_Load", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "CPU_Temp", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "DISK_Size", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "DISK_Temp", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "DISK_Usage", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "GPU_Load", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "GPU_Temp", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "GPU_Vram_Size", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "GPU_Vram_Usage", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "NET_In", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "NET_Out", - table: "Metrics"); - - migrationBuilder.DropColumn( - name: "RAM_Load", - table: "Metrics"); - - migrationBuilder.RenameColumn( - name: "Username", - table: "Users", - newName: "PreferredUsername"); - - migrationBuilder.RenameColumn( - name: "RAM_Size", - table: "Metrics", - newName: "Value"); - - migrationBuilder.AddColumn( - name: "PocketId", - table: "Users", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "ContainerId", - table: "Metrics", - type: "INTEGER", - nullable: true); - - migrationBuilder.AddColumn( - name: "Type", - table: "Metrics", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_Metrics_ContainerId", - table: "Metrics", - column: "ContainerId"); - - migrationBuilder.CreateIndex( - name: "IX_Metrics_ServerId", - table: "Metrics", - column: "ServerId"); - - migrationBuilder.AddForeignKey( - name: "FK_Metrics_Containers_ContainerId", - table: "Metrics", - column: "ContainerId", - principalTable: "Containers", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_Metrics_Servers_ServerId", - table: "Metrics", - column: "ServerId", - principalTable: "Servers", - principalColumn: "Id"); - } - } -} diff --git a/Watcher/Migrations/20250621125157_DB-Update Issue#32 IsVerified-Servers.Designer.cs b/Watcher/Migrations/20250621125157_DB-Update Issue#32 IsVerified-Servers.Designer.cs deleted file mode 100644 index feb5c2e..0000000 --- a/Watcher/Migrations/20250621125157_DB-Update Issue#32 IsVerified-Servers.Designer.cs +++ /dev/null @@ -1,319 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Watcher.Data; - -#nullable disable - -namespace Watcher.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20250621125157_DB-Update Issue#32 IsVerified-Servers")] - partial class DBUpdateIssue32IsVerifiedServers - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Hostname") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ImageId") - .HasColumnType("INTEGER"); - - b.Property("IsRunning") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.HasIndex("TagId"); - - b.ToTable("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Images"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("Level") - .HasColumnType("TEXT"); - - b.Property("Message") - .HasColumnType("TEXT"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("LogEvents"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CPU_Load") - .HasColumnType("REAL"); - - b.Property("CPU_Temp") - .HasColumnType("REAL"); - - b.Property("DISK_Size") - .HasColumnType("REAL"); - - b.Property("DISK_Temp") - .HasColumnType("REAL"); - - b.Property("DISK_Usage") - .HasColumnType("REAL"); - - b.Property("GPU_Load") - .HasColumnType("REAL"); - - b.Property("GPU_Temp") - .HasColumnType("REAL"); - - b.Property("GPU_Vram_Size") - .HasColumnType("REAL"); - - b.Property("GPU_Vram_Usage") - .HasColumnType("REAL"); - - b.Property("NET_In") - .HasColumnType("REAL"); - - b.Property("NET_Out") - .HasColumnType("REAL"); - - b.Property("RAM_Load") - .HasColumnType("REAL"); - - b.Property("RAM_Size") - .HasColumnType("REAL"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Metrics"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CpuCores") - .HasColumnType("INTEGER"); - - b.Property("CpuType") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnType("TEXT"); - - b.Property("GpuType") - .HasColumnType("TEXT"); - - b.Property("IPAddress") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("IsOnline") - .HasColumnType("INTEGER"); - - b.Property("IsVerified") - .HasColumnType("INTEGER"); - - b.Property("LastSeen") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RamSize") - .HasColumnType("REAL"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TagId"); - - b.ToTable("Servers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Watcher.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LastLogin") - .HasColumnType("TEXT"); - - b.Property("OIDC_Id") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.HasOne("Watcher.Models.Image", "Image") - .WithMany("Containers") - .HasForeignKey("ImageId"); - - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Containers") - .HasForeignKey("TagId"); - - b.Navigation("Image"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Servers") - .HasForeignKey("TagId"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Navigation("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Navigation("Containers"); - - b.Navigation("Servers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Watcher/Migrations/20250621125157_DB-Update Issue#32 IsVerified-Servers.cs b/Watcher/Migrations/20250621125157_DB-Update Issue#32 IsVerified-Servers.cs deleted file mode 100644 index 974f389..0000000 --- a/Watcher/Migrations/20250621125157_DB-Update Issue#32 IsVerified-Servers.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class DBUpdateIssue32IsVerifiedServers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "IsVerified", - table: "Servers", - type: "INTEGER", - nullable: false, - defaultValue: false); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "IsVerified", - table: "Servers"); - } - } -} diff --git a/Watcher/Migrations/20250710090920_container-attribute.Designer.cs b/Watcher/Migrations/20250710090920_container-attribute.Designer.cs deleted file mode 100644 index deb4c2c..0000000 --- a/Watcher/Migrations/20250710090920_container-attribute.Designer.cs +++ /dev/null @@ -1,332 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Watcher.Data; - -#nullable disable - -namespace Watcher.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20250710090920_container-attribute")] - partial class containerattribute - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("ExposedPort") - .HasColumnType("INTEGER"); - - b.Property("Health") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HostServerId") - .HasColumnType("INTEGER"); - - b.Property("Image") - .HasColumnType("TEXT"); - - b.Property("ImageId") - .HasColumnType("INTEGER"); - - b.Property("InternalPort") - .HasColumnType("INTEGER"); - - b.Property("IsRunning") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("HostServerId"); - - b.HasIndex("ImageId"); - - b.HasIndex("TagId"); - - b.ToTable("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Tag") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Images"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ContainerId") - .HasColumnType("INTEGER"); - - b.Property("Level") - .HasColumnType("TEXT"); - - b.Property("Message") - .HasColumnType("TEXT"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("ServerId"); - - b.ToTable("LogEvents"); - }); - - modelBuilder.Entity("Watcher.Models.Metric", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CPU_Load") - .HasColumnType("REAL"); - - b.Property("CPU_Temp") - .HasColumnType("REAL"); - - b.Property("DISK_Size") - .HasColumnType("REAL"); - - b.Property("DISK_Temp") - .HasColumnType("REAL"); - - b.Property("DISK_Usage") - .HasColumnType("REAL"); - - b.Property("GPU_Load") - .HasColumnType("REAL"); - - b.Property("GPU_Temp") - .HasColumnType("REAL"); - - b.Property("GPU_Vram_Size") - .HasColumnType("REAL"); - - b.Property("GPU_Vram_Usage") - .HasColumnType("REAL"); - - b.Property("NET_In") - .HasColumnType("REAL"); - - b.Property("NET_Out") - .HasColumnType("REAL"); - - b.Property("RAM_Load") - .HasColumnType("REAL"); - - b.Property("RAM_Size") - .HasColumnType("REAL"); - - b.Property("ServerId") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Metrics"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CpuCores") - .HasColumnType("INTEGER"); - - b.Property("CpuType") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Description") - .HasColumnType("TEXT"); - - b.Property("GpuType") - .HasColumnType("TEXT"); - - b.Property("IPAddress") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("IsOnline") - .HasColumnType("INTEGER"); - - b.Property("IsVerified") - .HasColumnType("INTEGER"); - - b.Property("LastSeen") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RamSize") - .HasColumnType("REAL"); - - b.Property("TagId") - .HasColumnType("INTEGER"); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TagId"); - - b.ToTable("Servers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Watcher.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LastLogin") - .HasColumnType("TEXT"); - - b.Property("OIDC_Id") - .HasColumnType("TEXT"); - - b.Property("Password") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Watcher.Models.Container", b => - { - b.HasOne("Watcher.Models.Server", "HostServer") - .WithMany() - .HasForeignKey("HostServerId"); - - b.HasOne("Watcher.Models.Image", null) - .WithMany("Containers") - .HasForeignKey("ImageId"); - - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Containers") - .HasForeignKey("TagId"); - - b.Navigation("HostServer"); - }); - - modelBuilder.Entity("Watcher.Models.LogEvent", b => - { - b.HasOne("Watcher.Models.Container", "Container") - .WithMany() - .HasForeignKey("ContainerId"); - - b.HasOne("Watcher.Models.Server", "Server") - .WithMany() - .HasForeignKey("ServerId"); - - b.Navigation("Container"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Watcher.Models.Server", b => - { - b.HasOne("Watcher.Models.Tag", null) - .WithMany("Servers") - .HasForeignKey("TagId"); - }); - - modelBuilder.Entity("Watcher.Models.Image", b => - { - b.Navigation("Containers"); - }); - - modelBuilder.Entity("Watcher.Models.Tag", b => - { - b.Navigation("Containers"); - - b.Navigation("Servers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Watcher/Migrations/20250710090920_container-attribute.cs b/Watcher/Migrations/20250710090920_container-attribute.cs deleted file mode 100644 index 7af4972..0000000 --- a/Watcher/Migrations/20250710090920_container-attribute.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class containerattribute : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Hostname", - table: "Containers"); - - migrationBuilder.RenameColumn( - name: "Type", - table: "Containers", - newName: "Health"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Containers", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AddColumn( - name: "ExposedPort", - table: "Containers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "HostServerId", - table: "Containers", - type: "INTEGER", - nullable: true); - - migrationBuilder.AddColumn( - name: "Image", - table: "Containers", - type: "TEXT", - nullable: true); - - migrationBuilder.AddColumn( - name: "InternalPort", - table: "Containers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - - migrationBuilder.CreateIndex( - name: "IX_Containers_HostServerId", - table: "Containers", - column: "HostServerId"); - - migrationBuilder.AddForeignKey( - name: "FK_Containers_Servers_HostServerId", - table: "Containers", - column: "HostServerId", - principalTable: "Servers", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Containers_Servers_HostServerId", - table: "Containers"); - - migrationBuilder.DropIndex( - name: "IX_Containers_HostServerId", - table: "Containers"); - - migrationBuilder.DropColumn( - name: "ExposedPort", - table: "Containers"); - - migrationBuilder.DropColumn( - name: "HostServerId", - table: "Containers"); - - migrationBuilder.DropColumn( - name: "Image", - table: "Containers"); - - migrationBuilder.DropColumn( - name: "InternalPort", - table: "Containers"); - - migrationBuilder.RenameColumn( - name: "Health", - table: "Containers", - newName: "Type"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Containers", - type: "TEXT", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AddColumn( - name: "Hostname", - table: "Containers", - type: "TEXT", - nullable: false, - defaultValue: ""); - } - } -} diff --git a/Watcher/Migrations/20250730113936_DiskSpace-ServerAttribute.cs b/Watcher/Migrations/20250730113936_DiskSpace-ServerAttribute.cs deleted file mode 100644 index a8fbd9f..0000000 --- a/Watcher/Migrations/20250730113936_DiskSpace-ServerAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class DiskSpaceServerAttribute : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "DiskSpace", - table: "Servers", - type: "TEXT", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "DiskSpace", - table: "Servers"); - } - } -} diff --git a/Watcher/Migrations/20250730172010_MeasurementWarnings.cs b/Watcher/Migrations/20250730172010_MeasurementWarnings.cs deleted file mode 100644 index f13e5c7..0000000 --- a/Watcher/Migrations/20250730172010_MeasurementWarnings.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Watcher.Migrations -{ - /// - public partial class MeasurementWarnings : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CPU_Load_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "CPU_Load_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "CPU_Temp_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "CPU_Temp_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "DISK_Temp_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "DISK_Temp_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "Disk_Usage_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "Disk_Usage_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Load_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Load_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Temp_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "GPU_Temp_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "RAM_Load_Critical", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - - migrationBuilder.AddColumn( - name: "RAM_Load_Warning", - table: "Servers", - type: "REAL", - nullable: false, - defaultValue: 0.0); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CPU_Load_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "CPU_Load_Warning", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "CPU_Temp_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "CPU_Temp_Warning", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "DISK_Temp_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "DISK_Temp_Warning", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "Disk_Usage_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "Disk_Usage_Warning", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "GPU_Load_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "GPU_Load_Warning", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "GPU_Temp_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "GPU_Temp_Warning", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "RAM_Load_Critical", - table: "Servers"); - - migrationBuilder.DropColumn( - name: "RAM_Load_Warning", - table: "Servers"); - } - } -} diff --git a/Watcher/Migrations/20250730172010_MeasurementWarnings.Designer.cs b/Watcher/Migrations/20251105183329_InitialMigration.Designer.cs similarity index 87% rename from Watcher/Migrations/20250730172010_MeasurementWarnings.Designer.cs rename to Watcher/Migrations/20251105183329_InitialMigration.Designer.cs index 25baf51..539298f 100644 --- a/Watcher/Migrations/20250730172010_MeasurementWarnings.Designer.cs +++ b/Watcher/Migrations/20251105183329_InitialMigration.Designer.cs @@ -11,8 +11,8 @@ using Watcher.Data; namespace Watcher.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20250730172010_MeasurementWarnings")] - partial class MeasurementWarnings + [Migration("20251105183329_InitialMigration")] + partial class InitialMigration { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,45 +26,33 @@ namespace Watcher.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("ExposedPort") - .HasColumnType("INTEGER"); - - b.Property("Health") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HostServerId") - .HasColumnType("INTEGER"); + b.Property("ContainerId") + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "id"); b.Property("Image") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "image"); b.Property("ImageId") .HasColumnType("INTEGER"); - b.Property("InternalPort") - .HasColumnType("INTEGER"); - b.Property("IsRunning") .HasColumnType("INTEGER"); b.Property("Name") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "name"); - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasAnnotation("Relational:JsonPropertyName", "Server_id"); b.Property("TagId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("HostServerId"); - b.HasIndex("ImageId"); b.HasIndex("TagId"); @@ -72,6 +60,35 @@ namespace Watcher.Migrations b.ToTable("Containers"); }); + modelBuilder.Entity("Watcher.Models.ContainerMetric", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CPU_Load") + .HasColumnType("REAL"); + + b.Property("CPU_Temp") + .HasColumnType("REAL"); + + b.Property("ContainerId") + .HasColumnType("INTEGER"); + + b.Property("RAM_Load") + .HasColumnType("REAL"); + + b.Property("RAM_Size") + .HasColumnType("REAL"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ContainerMetrics"); + }); + modelBuilder.Entity("Watcher.Models.Image", b => { b.Property("Id") @@ -298,22 +315,16 @@ namespace Watcher.Migrations b.Property("Email") .HasColumnType("TEXT"); - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("LastLogin") .HasColumnType("TEXT"); - b.Property("OIDC_Id") - .HasColumnType("TEXT"); - b.Property("Password") .IsRequired() .HasColumnType("TEXT"); b.Property("Username") .IsRequired() + .HasMaxLength(50) .HasColumnType("TEXT"); b.HasKey("Id"); @@ -323,10 +334,6 @@ namespace Watcher.Migrations modelBuilder.Entity("Watcher.Models.Container", b => { - b.HasOne("Watcher.Models.Server", "HostServer") - .WithMany() - .HasForeignKey("HostServerId"); - b.HasOne("Watcher.Models.Image", null) .WithMany("Containers") .HasForeignKey("ImageId"); @@ -334,8 +341,6 @@ namespace Watcher.Migrations b.HasOne("Watcher.Models.Tag", null) .WithMany("Containers") .HasForeignKey("TagId"); - - b.Navigation("HostServer"); }); modelBuilder.Entity("Watcher.Models.LogEvent", b => diff --git a/Watcher/Migrations/20251105183329_InitialMigration.cs b/Watcher/Migrations/20251105183329_InitialMigration.cs new file mode 100644 index 0000000..253bf68 --- /dev/null +++ b/Watcher/Migrations/20251105183329_InitialMigration.cs @@ -0,0 +1,257 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Watcher.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ContainerMetrics", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Timestamp = table.Column(type: "TEXT", nullable: false), + ContainerId = table.Column(type: "INTEGER", nullable: true), + CPU_Load = table.Column(type: "REAL", nullable: false), + CPU_Temp = table.Column(type: "REAL", nullable: false), + RAM_Size = table.Column(type: "REAL", nullable: false), + RAM_Load = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ContainerMetrics", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Images", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: true), + Tag = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Images", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Metrics", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Timestamp = table.Column(type: "TEXT", nullable: false), + ServerId = table.Column(type: "INTEGER", nullable: true), + CPU_Load = table.Column(type: "REAL", nullable: false), + CPU_Temp = table.Column(type: "REAL", nullable: false), + GPU_Load = table.Column(type: "REAL", nullable: false), + GPU_Temp = table.Column(type: "REAL", nullable: false), + GPU_Vram_Size = table.Column(type: "REAL", nullable: false), + GPU_Vram_Usage = table.Column(type: "REAL", nullable: false), + RAM_Size = table.Column(type: "REAL", nullable: false), + RAM_Load = table.Column(type: "REAL", nullable: false), + DISK_Size = table.Column(type: "REAL", nullable: false), + DISK_Usage = table.Column(type: "REAL", nullable: false), + DISK_Temp = table.Column(type: "REAL", nullable: false), + NET_In = table.Column(type: "REAL", nullable: false), + NET_Out = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Metrics", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Tags", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tags", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Username = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Email = table.Column(type: "TEXT", nullable: true), + LastLogin = table.Column(type: "TEXT", nullable: false), + Password = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Containers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ServerId = table.Column(type: "INTEGER", nullable: false), + ContainerId = table.Column(type: "TEXT", nullable: true), + Image = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", nullable: true), + IsRunning = table.Column(type: "INTEGER", nullable: false), + ImageId = table.Column(type: "INTEGER", nullable: true), + TagId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Containers", x => x.Id); + table.ForeignKey( + name: "FK_Containers_Images_ImageId", + column: x => x.ImageId, + principalTable: "Images", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Containers_Tags_TagId", + column: x => x.TagId, + principalTable: "Tags", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Servers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + IPAddress = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: true), + CpuType = table.Column(type: "TEXT", nullable: true), + CpuCores = table.Column(type: "INTEGER", nullable: false), + GpuType = table.Column(type: "TEXT", nullable: true), + RamSize = table.Column(type: "REAL", nullable: false), + DiskSpace = table.Column(type: "TEXT", nullable: true), + CPU_Load_Warning = table.Column(type: "REAL", nullable: false), + CPU_Load_Critical = table.Column(type: "REAL", nullable: false), + CPU_Temp_Warning = table.Column(type: "REAL", nullable: false), + CPU_Temp_Critical = table.Column(type: "REAL", nullable: false), + RAM_Load_Warning = table.Column(type: "REAL", nullable: false), + RAM_Load_Critical = table.Column(type: "REAL", nullable: false), + GPU_Load_Warning = table.Column(type: "REAL", nullable: false), + GPU_Load_Critical = table.Column(type: "REAL", nullable: false), + GPU_Temp_Warning = table.Column(type: "REAL", nullable: false), + GPU_Temp_Critical = table.Column(type: "REAL", nullable: false), + Disk_Usage_Warning = table.Column(type: "REAL", nullable: false), + Disk_Usage_Critical = table.Column(type: "REAL", nullable: false), + DISK_Temp_Warning = table.Column(type: "REAL", nullable: false), + DISK_Temp_Critical = table.Column(type: "REAL", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + IsOnline = table.Column(type: "INTEGER", nullable: false), + LastSeen = table.Column(type: "TEXT", nullable: false), + IsVerified = table.Column(type: "INTEGER", nullable: false), + TagId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Servers", x => x.Id); + table.ForeignKey( + name: "FK_Servers_Tags_TagId", + column: x => x.TagId, + principalTable: "Tags", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "LogEvents", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Timestamp = table.Column(type: "TEXT", nullable: false), + Message = table.Column(type: "TEXT", nullable: true), + Level = table.Column(type: "TEXT", nullable: true), + ServerId = table.Column(type: "INTEGER", nullable: true), + ContainerId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LogEvents", x => x.Id); + table.ForeignKey( + name: "FK_LogEvents_Containers_ContainerId", + column: x => x.ContainerId, + principalTable: "Containers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_LogEvents_Servers_ServerId", + column: x => x.ServerId, + principalTable: "Servers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Containers_ImageId", + table: "Containers", + column: "ImageId"); + + migrationBuilder.CreateIndex( + name: "IX_Containers_TagId", + table: "Containers", + column: "TagId"); + + migrationBuilder.CreateIndex( + name: "IX_LogEvents_ContainerId", + table: "LogEvents", + column: "ContainerId"); + + migrationBuilder.CreateIndex( + name: "IX_LogEvents_ServerId", + table: "LogEvents", + column: "ServerId"); + + migrationBuilder.CreateIndex( + name: "IX_Servers_TagId", + table: "Servers", + column: "TagId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ContainerMetrics"); + + migrationBuilder.DropTable( + name: "LogEvents"); + + migrationBuilder.DropTable( + name: "Metrics"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "Containers"); + + migrationBuilder.DropTable( + name: "Servers"); + + migrationBuilder.DropTable( + name: "Images"); + + migrationBuilder.DropTable( + name: "Tags"); + } + } +} diff --git a/Watcher/Migrations/20250730113936_DiskSpace-ServerAttribute.Designer.cs b/Watcher/Migrations/20251105201056_AddContainerServerNavigation.Designer.cs similarity index 73% rename from Watcher/Migrations/20250730113936_DiskSpace-ServerAttribute.Designer.cs rename to Watcher/Migrations/20251105201056_AddContainerServerNavigation.Designer.cs index d33eb83..bc2eae9 100644 --- a/Watcher/Migrations/20250730113936_DiskSpace-ServerAttribute.Designer.cs +++ b/Watcher/Migrations/20251105201056_AddContainerServerNavigation.Designer.cs @@ -11,8 +11,8 @@ using Watcher.Data; namespace Watcher.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20250730113936_DiskSpace-ServerAttribute")] - partial class DiskSpaceServerAttribute + [Migration("20251105201056_AddContainerServerNavigation")] + partial class AddContainerServerNavigation { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,52 +26,71 @@ namespace Watcher.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("ExposedPort") - .HasColumnType("INTEGER"); - - b.Property("Health") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HostServerId") - .HasColumnType("INTEGER"); + b.Property("ContainerId") + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "id"); b.Property("Image") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "image"); b.Property("ImageId") .HasColumnType("INTEGER"); - b.Property("InternalPort") - .HasColumnType("INTEGER"); - b.Property("IsRunning") .HasColumnType("INTEGER"); b.Property("Name") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "name"); - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasAnnotation("Relational:JsonPropertyName", "Server_id"); b.Property("TagId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("HostServerId"); - b.HasIndex("ImageId"); + b.HasIndex("ServerId"); + b.HasIndex("TagId"); b.ToTable("Containers"); }); + modelBuilder.Entity("Watcher.Models.ContainerMetric", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CPU_Load") + .HasColumnType("REAL"); + + b.Property("CPU_Temp") + .HasColumnType("REAL"); + + b.Property("ContainerId") + .HasColumnType("INTEGER"); + + b.Property("RAM_Load") + .HasColumnType("REAL"); + + b.Property("RAM_Size") + .HasColumnType("REAL"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ContainerMetrics"); + }); + modelBuilder.Entity("Watcher.Models.Image", b => { b.Property("Id") @@ -181,6 +200,18 @@ namespace Watcher.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("CPU_Load_Critical") + .HasColumnType("REAL"); + + b.Property("CPU_Load_Warning") + .HasColumnType("REAL"); + + b.Property("CPU_Temp_Critical") + .HasColumnType("REAL"); + + b.Property("CPU_Temp_Warning") + .HasColumnType("REAL"); + b.Property("CpuCores") .HasColumnType("INTEGER"); @@ -190,12 +221,36 @@ namespace Watcher.Migrations b.Property("CreatedAt") .HasColumnType("TEXT"); + b.Property("DISK_Temp_Critical") + .HasColumnType("REAL"); + + b.Property("DISK_Temp_Warning") + .HasColumnType("REAL"); + b.Property("Description") .HasColumnType("TEXT"); b.Property("DiskSpace") .HasColumnType("TEXT"); + b.Property("Disk_Usage_Critical") + .HasColumnType("REAL"); + + b.Property("Disk_Usage_Warning") + .HasColumnType("REAL"); + + b.Property("GPU_Load_Critical") + .HasColumnType("REAL"); + + b.Property("GPU_Load_Warning") + .HasColumnType("REAL"); + + b.Property("GPU_Temp_Critical") + .HasColumnType("REAL"); + + b.Property("GPU_Temp_Warning") + .HasColumnType("REAL"); + b.Property("GpuType") .HasColumnType("TEXT"); @@ -216,6 +271,12 @@ namespace Watcher.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("RAM_Load_Critical") + .HasColumnType("REAL"); + + b.Property("RAM_Load_Warning") + .HasColumnType("REAL"); + b.Property("RamSize") .HasColumnType("REAL"); @@ -256,22 +317,16 @@ namespace Watcher.Migrations b.Property("Email") .HasColumnType("TEXT"); - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("LastLogin") .HasColumnType("TEXT"); - b.Property("OIDC_Id") - .HasColumnType("TEXT"); - b.Property("Password") .IsRequired() .HasColumnType("TEXT"); b.Property("Username") .IsRequired() + .HasMaxLength(50) .HasColumnType("TEXT"); b.HasKey("Id"); @@ -281,19 +336,21 @@ namespace Watcher.Migrations modelBuilder.Entity("Watcher.Models.Container", b => { - b.HasOne("Watcher.Models.Server", "HostServer") - .WithMany() - .HasForeignKey("HostServerId"); - b.HasOne("Watcher.Models.Image", null) .WithMany("Containers") .HasForeignKey("ImageId"); + b.HasOne("Watcher.Models.Server", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.HasOne("Watcher.Models.Tag", null) .WithMany("Containers") .HasForeignKey("TagId"); - b.Navigation("HostServer"); + b.Navigation("Server"); }); modelBuilder.Entity("Watcher.Models.LogEvent", b => diff --git a/Watcher/Migrations/20251105201056_AddContainerServerNavigation.cs b/Watcher/Migrations/20251105201056_AddContainerServerNavigation.cs new file mode 100644 index 0000000..b03c260 --- /dev/null +++ b/Watcher/Migrations/20251105201056_AddContainerServerNavigation.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Watcher.Migrations +{ + /// + public partial class AddContainerServerNavigation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Containers_ServerId", + table: "Containers", + column: "ServerId"); + + migrationBuilder.AddForeignKey( + name: "FK_Containers_Servers_ServerId", + table: "Containers", + column: "ServerId", + principalTable: "Servers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Containers_Servers_ServerId", + table: "Containers"); + + migrationBuilder.DropIndex( + name: "IX_Containers_ServerId", + table: "Containers"); + } + } +} diff --git a/Watcher/Migrations/AppDbContextModelSnapshot.cs b/Watcher/Migrations/AppDbContextModelSnapshot.cs index 7fee30d..99d7352 100644 --- a/Watcher/Migrations/AppDbContextModelSnapshot.cs +++ b/Watcher/Migrations/AppDbContextModelSnapshot.cs @@ -23,52 +23,71 @@ namespace Watcher.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("ExposedPort") - .HasColumnType("INTEGER"); - - b.Property("Health") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HostServerId") - .HasColumnType("INTEGER"); + b.Property("ContainerId") + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "id"); b.Property("Image") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "image"); b.Property("ImageId") .HasColumnType("INTEGER"); - b.Property("InternalPort") - .HasColumnType("INTEGER"); - b.Property("IsRunning") .HasColumnType("INTEGER"); b.Property("Name") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasAnnotation("Relational:JsonPropertyName", "name"); - b.Property("Status") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasAnnotation("Relational:JsonPropertyName", "Server_id"); b.Property("TagId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("HostServerId"); - b.HasIndex("ImageId"); + b.HasIndex("ServerId"); + b.HasIndex("TagId"); b.ToTable("Containers"); }); + modelBuilder.Entity("Watcher.Models.ContainerMetric", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CPU_Load") + .HasColumnType("REAL"); + + b.Property("CPU_Temp") + .HasColumnType("REAL"); + + b.Property("ContainerId") + .HasColumnType("INTEGER"); + + b.Property("RAM_Load") + .HasColumnType("REAL"); + + b.Property("RAM_Size") + .HasColumnType("REAL"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ContainerMetrics"); + }); + modelBuilder.Entity("Watcher.Models.Image", b => { b.Property("Id") @@ -295,22 +314,16 @@ namespace Watcher.Migrations b.Property("Email") .HasColumnType("TEXT"); - b.Property("IdentityProvider") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("LastLogin") .HasColumnType("TEXT"); - b.Property("OIDC_Id") - .HasColumnType("TEXT"); - b.Property("Password") .IsRequired() .HasColumnType("TEXT"); b.Property("Username") .IsRequired() + .HasMaxLength(50) .HasColumnType("TEXT"); b.HasKey("Id"); @@ -320,19 +333,21 @@ namespace Watcher.Migrations modelBuilder.Entity("Watcher.Models.Container", b => { - b.HasOne("Watcher.Models.Server", "HostServer") - .WithMany() - .HasForeignKey("HostServerId"); - b.HasOne("Watcher.Models.Image", null) .WithMany("Containers") .HasForeignKey("ImageId"); + b.HasOne("Watcher.Models.Server", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.HasOne("Watcher.Models.Tag", null) .WithMany("Containers") .HasForeignKey("TagId"); - b.Navigation("HostServer"); + b.Navigation("Server"); }); modelBuilder.Entity("Watcher.Models.LogEvent", b => diff --git a/Watcher/Models/Container.cs b/Watcher/Models/Container.cs index 0101b4f..826dcbe 100644 --- a/Watcher/Models/Container.cs +++ b/Watcher/Models/Container.cs @@ -1,25 +1,27 @@ +using System.Text.Json.Serialization; + namespace Watcher.Models; public class Container { public int Id { get; set; } - // Container Details - public string? Name { get; set; } + [JsonPropertyName("Server_id")] + public int ServerId { get; set; } - public int ExposedPort { get; set; } + // Navigation Property + public Server Server { get; set; } = null!; - public int InternalPort { get; set; } + [JsonPropertyName("id")] + public String? ContainerId { get; set; } - public string Status { get; set; } = string.Empty; + [JsonPropertyName("image")] + public String? Image { get; set; } - public string Health { get; set; } = string.Empty; - - public string? Image { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + [JsonPropertyName("name")] + public String? Name { get; set; } + // keine Variable, die vom Agent übergeben wird. Ein container ist immer Running, die Variable dient nur für die Übersicht + // auf dem Dashboard. public Boolean IsRunning { get; set; } = true; - - public Server? HostServer { get; set; } } diff --git a/Watcher/Models/ContainerMetric.cs b/Watcher/Models/ContainerMetric.cs new file mode 100644 index 0000000..5c95d99 --- /dev/null +++ b/Watcher/Models/ContainerMetric.cs @@ -0,0 +1,24 @@ +namespace Watcher.Models; + +public class ContainerMetric +{ + // Metric Metadata + public int Id { get; set; } + public DateTime Timestamp { get; set; } + + + // Zuordnung zu einem Container -- Foreign Key + public int? ContainerId { get; set; } + + + // CPU-Daten + public double CPU_Load { get; set; } = 0.0; // % + + public double CPU_Temp { get; set; } = 0.0; // deg C + + // RAM-Daten + public double RAM_Size { get; set; } = 0.0; // GB + + public double RAM_Load { get; set; } = 0.0; // % + +} diff --git a/Watcher/Models/User.cs b/Watcher/Models/User.cs index 13d366a..ce774d7 100644 --- a/Watcher/Models/User.cs +++ b/Watcher/Models/User.cs @@ -7,16 +7,18 @@ public class User { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } // PK - public string? OIDC_Id { get; set; } = null!; - public string Username { get; set; } = null!; - public string? Email { get; set; } - public DateTime LastLogin { get; set; } + public int Id { get; set; } [Required] - public string IdentityProvider { get; set; } = "local"; - + [StringLength(50)] + public string Username { get; set; } = null!; + + [EmailAddress] + public string? Email { get; set; } + + public DateTime LastLogin { get; set; } = DateTime.UtcNow; + [Required] [DataType(DataType.Password)] - public String? Password { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; } diff --git a/Watcher/Program.cs b/Watcher/Program.cs index 60e3133..d6fab21 100644 --- a/Watcher/Program.cs +++ b/Watcher/Program.cs @@ -1,13 +1,11 @@ -using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.EntityFrameworkCore; +using Microsoft.OpenApi.Models; using Serilog; using Watcher.Data; using Watcher.Models; using Watcher.Services; -//using Watcher.Services; -//using Watcher.Workers; var builder = WebApplication.CreateBuilder(args); @@ -36,11 +34,21 @@ builder.Services.AddHttpContextAccessor(); // Storage Singleton builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // Background Services builder.Services.AddHostedService(); builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +// Swagger API-Dokumentation +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc("v1", new OpenApiInfo { Title = "Watcher-Server API", Version = "v1" }); +}); // ---------- Konfiguration ---------- DotNetEnv.Env.Load(); @@ -55,106 +63,25 @@ var configuration = builder.Configuration; // ---------- DB-Kontext ---------- -var dbProvider = configuration["Database:Provider"] ?? "MySQL"; -var connectionString = configuration["Database:ConnectionString"]; +// Nur SQLite wird unterstützt +var sqliteConnectionString = configuration.GetConnectionString("Sqlite") + ?? configuration["Database:ConnectionStrings:Sqlite"] + ?? "Data Source=./persistence/watcher.db"; + builder.Services.AddDbContext((serviceProvider, options) => { - var config = serviceProvider.GetRequiredService(); - var provider = dbProvider; - - if (provider == "MySql") - { - var connStr = config.GetConnectionString("MySql") ?? config["Database:ConnectionStrings:MySql"]; - options.UseMySql(connStr, ServerVersion.AutoDetect(connStr)); - } - else if (provider == "Sqlite") - { - var connStr = config.GetConnectionString("Sqlite") ?? config["Database:ConnectionStrings:Sqlite"]; - options.UseSqlite(connStr); - } - else - { - throw new Exception("Unsupported database provider configured."); - } + options.UseSqlite(sqliteConnectionString); }); // ---------- Authentifizierung konfigurieren ---------- -// PocketID nur konfigurieren, wenn aktiviert -var pocketIdSection = builder.Configuration.GetSection("Authentication:PocketID"); -var pocketIdEnabled = pocketIdSection.GetValue("Enabled"); - - -var auth = builder.Services.AddAuthentication("Cookies"); -auth.AddCookie("Cookies", options => -{ - options.LoginPath = "/Auth/Login"; - options.AccessDeniedPath = "/Auth/AccessDenied"; -}); - - -builder.Services.AddAuthentication() -.AddOpenIdConnect("oidc", options => -{ - options.Authority = pocketIdSection["Authority"]; - options.ClientId = pocketIdSection["ClientId"]; - options.ClientSecret = pocketIdSection["ClientSecret"]; - options.ResponseType = "code"; - options.CallbackPath = pocketIdSection["CallbackPath"]; - options.SaveTokens = true; - - options.GetClaimsFromUserInfoEndpoint = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("email"); - - options.Events = new OpenIdConnectEvents +// Nur Cookie-basierte lokale Authentifizierung +builder.Services.AddAuthentication("Cookies") + .AddCookie("Cookies", options => { - OnTokenValidated = async ctx => - { - var db = ctx.HttpContext.RequestServices.GetRequiredService(); - - 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; - - if (string.IsNullOrEmpty(pocketId)) - return; - - var user = await db.Users.FirstOrDefaultAsync(u => u.OIDC_Id == pocketId); - - if (user == null) - { - user = new User - { - OIDC_Id = pocketId, - Username = preferredUsername ?? "", - Email = email, - LastLogin = DateTime.UtcNow, - IdentityProvider = "oidc", - Password = string.Empty - }; - db.Users.Add(user); - } - else - { - user.LastLogin = DateTime.UtcNow; - user.Username = preferredUsername ?? user.Username; - user.Email = email ?? user.Email; - db.Users.Update(user); - } - - await db.SaveChangesAsync(); - } - }; -}); + options.LoginPath = "/Auth/Login"; + options.AccessDeniedPath = "/Auth/AccessDenied"; + }); var app = builder.Build(); @@ -168,7 +95,7 @@ using (var scope = app.Services.CreateScope()) } -// Standart-User in Datenbank schreiben +// Standard-Admin-User erstellen using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); @@ -177,21 +104,19 @@ using (var scope = app.Services.CreateScope()) if (!db.Users.Any()) { - Console.WriteLine("No users found, creating default user..."); + Console.WriteLine("No users found, creating default admin user..."); var defaultUser = new User { - OIDC_Id = string.Empty, Username = "admin", - Email = string.Empty, + Email = "admin@localhost", LastLogin = DateTime.UtcNow, - IdentityProvider = "local", Password = BCrypt.Net.BCrypt.HashPassword("changeme") }; db.Users.Add(defaultUser); db.SaveChanges(); - Console.WriteLine("Default user created."); + Console.WriteLine("Default admin user created (username: admin, password: changeme)"); } else { @@ -204,22 +129,30 @@ using (var scope = app.Services.CreateScope()) if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } - app.UseHttpsRedirection(); +app.UseStaticFiles(); + app.UseRouting(); +// 🔹 Swagger aktivieren +app.UseSwagger(); +app.UseSwaggerUI(options => +{ + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Watcher-Server API v1"); + options.RoutePrefix = "api/v1/swagger"; +}); + +// 🔹 Authentifizierung & Autorisierung app.UseAuthentication(); app.UseAuthorization(); -app.UseStaticFiles(); - +// 🔹 MVC-Routing app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}" - ); +); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/Watcher/Services/ISystemStore.cs b/Watcher/Services/ISystemStore.cs new file mode 100644 index 0000000..1ee6d22 --- /dev/null +++ b/Watcher/Services/ISystemStore.cs @@ -0,0 +1,9 @@ +namespace Watcher.Services; + +public interface ISystemStore +{ + Boolean NewVersionAvailable { get; set; } + + Double DatabaseSize { get; set; } + +} \ No newline at end of file diff --git a/Watcher/Services/IUpdateCheckStore.cs b/Watcher/Services/IUpdateCheckStore.cs new file mode 100644 index 0000000..ead0e00 --- /dev/null +++ b/Watcher/Services/IUpdateCheckStore.cs @@ -0,0 +1,8 @@ +namespace Watcher.Services; + +public interface IUpdateCheckStore +{ + bool IsUpdateAvailable { get; set; } + string? LatestVersion { get; set; } + DateTime LastChecked { get; set; } +} diff --git a/Watcher/Services/IVersionService.cs b/Watcher/Services/IVersionService.cs new file mode 100644 index 0000000..673754f --- /dev/null +++ b/Watcher/Services/IVersionService.cs @@ -0,0 +1,6 @@ +namespace Watcher.Services; + +public interface IVersionService +{ + string GetVersion(); +} diff --git a/Watcher/Services/MetricCleanupService.cs b/Watcher/Services/MetricCleanupService.cs new file mode 100644 index 0000000..a7cafa6 --- /dev/null +++ b/Watcher/Services/MetricCleanupService.cs @@ -0,0 +1,127 @@ +using Microsoft.EntityFrameworkCore; +using Watcher.Data; + +namespace Watcher.Services; + +public class MetricCleanupService : BackgroundService +{ + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + private readonly int _retentionDays; + private readonly int _checkIntervalHours; + private readonly bool _enabled; + + public MetricCleanupService( + ILogger logger, + IServiceProvider serviceProvider, + IConfiguration configuration) + { + _logger = logger; + _serviceProvider = serviceProvider; + + // Konfiguration aus Environment Variablen laden + _retentionDays = int.TryParse( + configuration["DataRetention:MetricRetentionDays"] ?? + Environment.GetEnvironmentVariable("METRIC_RETENTION_DAYS"), + out var days) ? days : 30; // Default: 30 Tage + + _checkIntervalHours = int.TryParse( + configuration["DataRetention:CleanupIntervalHours"] ?? + Environment.GetEnvironmentVariable("METRIC_CLEANUP_INTERVAL_HOURS"), + out var hours) ? hours : 24; // Default: 24 Stunden + + _enabled = bool.TryParse( + configuration["DataRetention:Enabled"] ?? + Environment.GetEnvironmentVariable("METRIC_CLEANUP_ENABLED"), + out var enabled) ? enabled : true; // Default: aktiviert + + _logger.LogInformation( + "MetricCleanupService konfiguriert: Enabled={Enabled}, RetentionDays={Days}, IntervalHours={Hours}", + _enabled, _retentionDays, _checkIntervalHours); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (!_enabled) + { + _logger.LogInformation("MetricCleanupService ist deaktiviert."); + return; + } + + // Warte 1 Minute nach Start, bevor der erste Cleanup läuft + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + + var timer = new PeriodicTimer(TimeSpan.FromHours(_checkIntervalHours)); + + while (await timer.WaitForNextTickAsync(stoppingToken)) + { + try + { + await CleanupOldMetricsAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Fehler beim Cleanup alter Metriken."); + } + + // Offset nach Cleanup + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + } + } + + private async Task CleanupOldMetricsAsync() + { + _logger.LogInformation("Starte Metric Cleanup für Daten älter als {Days} Tage...", _retentionDays); + + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var cutoffDate = DateTime.UtcNow.AddDays(-_retentionDays); + + try + { + // Anzahl der zu löschenden Einträge ermitteln + var countToDelete = await context.Metrics + .Where(m => m.Timestamp < cutoffDate) + .CountAsync(); + + if (countToDelete == 0) + { + _logger.LogInformation("Keine alten Metriken zum Löschen gefunden."); + return; + } + + _logger.LogInformation("Lösche {Count} Metriken vor {Date}...", countToDelete, cutoffDate); + + // Metriken löschen + var deletedCount = await context.Metrics + .Where(m => m.Timestamp < cutoffDate) + .ExecuteDeleteAsync(); + + _logger.LogInformation( + "Metric Cleanup abgeschlossen: {DeletedCount} Einträge gelöscht.", + deletedCount); + + // Optional: ContainerMetrics auch bereinigen + var containerMetricsCount = await context.ContainerMetrics + .Where(cm => cm.Timestamp < cutoffDate) + .CountAsync(); + + if (containerMetricsCount > 0) + { + var deletedContainerMetrics = await context.ContainerMetrics + .Where(cm => cm.Timestamp < cutoffDate) + .ExecuteDeleteAsync(); + + _logger.LogInformation( + "ContainerMetrics Cleanup: {DeletedCount} Einträge gelöscht.", + deletedContainerMetrics); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Fehler beim Löschen alter Metriken aus der Datenbank."); + throw; + } + } +} diff --git a/Watcher/Services/SystemMangement.cs b/Watcher/Services/SystemMangement.cs new file mode 100644 index 0000000..efd8dcc --- /dev/null +++ b/Watcher/Services/SystemMangement.cs @@ -0,0 +1,42 @@ +using System.Net.Http; + +namespace Watcher.Services; + +public class SystemManagement : BackgroundService +{ + private readonly ILogger _logger; + + private ISystemStore _SystemStore; + + public SystemManagement(ILogger logger, ISystemStore SystemStore) + { + _logger = logger; + _SystemStore = SystemStore; + + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // Todo: Umstellen auf einmal alle 24h + var timer = new PeriodicTimer(TimeSpan.FromSeconds(30)); + + while (await timer.WaitForNextTickAsync(stoppingToken)) + { + // Hintergrundprozess abwarten + await checkForNewDockerImageVersion(); + + // 5 Sekdunden Offset + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + + public Task checkForNewDockerImageVersion() + { + return Task.CompletedTask; + } + + public Task createDailySqliteBackup() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Watcher/Services/SystemStore.cs b/Watcher/Services/SystemStore.cs new file mode 100644 index 0000000..34fce64 --- /dev/null +++ b/Watcher/Services/SystemStore.cs @@ -0,0 +1,9 @@ +namespace Watcher.Services; + +public class SystemStore: ISystemStore +{ + public Boolean NewVersionAvailable { get; set; } + + public Double DatabaseSize { get; set; } + +} \ No newline at end of file diff --git a/Watcher/Services/UpdateCheckService.cs b/Watcher/Services/UpdateCheckService.cs new file mode 100644 index 0000000..27377cb --- /dev/null +++ b/Watcher/Services/UpdateCheckService.cs @@ -0,0 +1,159 @@ +using System.Text.Json; + +namespace Watcher.Services; + +public class UpdateCheckService : BackgroundService +{ + private readonly ILogger _logger; + private readonly IUpdateCheckStore _updateCheckStore; + private readonly IVersionService _versionService; + private readonly string _repositoryUrl; + private readonly int _checkIntervalHours; + private readonly bool _enabled; + private readonly HttpClient _httpClient; + + public UpdateCheckService( + ILogger logger, + IUpdateCheckStore updateCheckStore, + IVersionService versionService, + IConfiguration configuration) + { + _logger = logger; + _updateCheckStore = updateCheckStore; + _versionService = versionService; + _httpClient = new HttpClient(); + + // Konfiguration aus Environment Variablen laden + _repositoryUrl = configuration["UpdateCheck:RepositoryUrl"] + ?? Environment.GetEnvironmentVariable("UPDATE_CHECK_REPOSITORY_URL") + ?? "https://git.triggermeelmo.com/api/v1/repos/Watcher/watcher/releases/latest"; + + _checkIntervalHours = int.TryParse( + configuration["UpdateCheck:CheckIntervalHours"] + ?? Environment.GetEnvironmentVariable("UPDATE_CHECK_INTERVAL_HOURS"), + out var hours) ? hours : 24; // Default: 24 Stunden + + _enabled = bool.TryParse( + configuration["UpdateCheck:Enabled"] + ?? Environment.GetEnvironmentVariable("UPDATE_CHECK_ENABLED"), + out var enabled) ? enabled : true; // Default: aktiviert + + _logger.LogInformation( + "UpdateCheckService konfiguriert: Enabled={Enabled}, Repository={Repo}, IntervalHours={Hours}", + _enabled, _repositoryUrl, _checkIntervalHours); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (!_enabled) + { + _logger.LogInformation("UpdateCheckService ist deaktiviert."); + return; + } + + // Warte 2 Minuten nach Start, bevor der erste Check läuft + await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken); + + var timer = new PeriodicTimer(TimeSpan.FromHours(_checkIntervalHours)); + + while (await timer.WaitForNextTickAsync(stoppingToken)) + { + try + { + await CheckForUpdatesAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Fehler beim Update-Check."); + } + + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + } + } + + private async Task CheckForUpdatesAsync() + { + var currentVersion = _versionService.GetVersion(); + + _logger.LogInformation("Starte Update-Check. Aktuelle Version: {Version}", currentVersion); + + // Bei "development" oder "latest" immer als aktuell markieren + if (currentVersion == "development" || currentVersion == "latest") + { + _updateCheckStore.IsUpdateAvailable = false; + _updateCheckStore.LatestVersion = currentVersion; + _updateCheckStore.LastChecked = DateTime.UtcNow; + _logger.LogInformation("Development/Latest Build - keine Update-Prüfung nötig."); + return; + } + + try + { + // Gitea API abfragen + var response = await _httpClient.GetAsync(_repositoryUrl); + + if (!response.IsSuccessStatusCode) + { + _logger.LogWarning("Gitea API Fehler: {StatusCode}", response.StatusCode); + return; + } + + var jsonContent = await response.Content.ReadAsStringAsync(); + var releaseInfo = JsonSerializer.Deserialize(jsonContent); + + if (releaseInfo?.TagName == null) + { + _logger.LogWarning("Keine Release-Information gefunden."); + return; + } + + var latestVersion = releaseInfo.TagName; + _updateCheckStore.LatestVersion = latestVersion; + _updateCheckStore.LastChecked = DateTime.UtcNow; + + // Versionsvergleich + var isNewer = CompareVersions(latestVersion, currentVersion); + _updateCheckStore.IsUpdateAvailable = isNewer; + + if (isNewer) + { + _logger.LogInformation( + "Neue Version verfügbar: {Latest} (aktuell: {Current})", + latestVersion, currentVersion); + } + else + { + _logger.LogInformation( + "System ist auf dem neuesten Stand: {Version}", + currentVersion); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Fehler beim Abrufen der Release-Informationen von Gitea."); + } + } + + private bool CompareVersions(string latestVersion, string currentVersion) + { + // Entferne "v" Prefix falls vorhanden + latestVersion = latestVersion.TrimStart('v'); + currentVersion = currentVersion.TrimStart('v'); + + // Versuche semantic versioning zu parsen + if (Version.TryParse(latestVersion, out var latest) && + Version.TryParse(currentVersion, out var current)) + { + return latest > current; + } + + // Fallback: String-Vergleich + return string.Compare(latestVersion, currentVersion, StringComparison.Ordinal) > 0; + } + + // DTO für Gitea API Response + private class GiteaReleaseResponse + { + public string? TagName { get; set; } + } +} diff --git a/Watcher/Services/UpdateCheckStore.cs b/Watcher/Services/UpdateCheckStore.cs new file mode 100644 index 0000000..5cca773 --- /dev/null +++ b/Watcher/Services/UpdateCheckStore.cs @@ -0,0 +1,8 @@ +namespace Watcher.Services; + +public class UpdateCheckStore : IUpdateCheckStore +{ + public bool IsUpdateAvailable { get; set; } = false; + public string? LatestVersion { get; set; } = null; + public DateTime LastChecked { get; set; } = DateTime.MinValue; +} diff --git a/Watcher/Services/VersionService.cs b/Watcher/Services/VersionService.cs new file mode 100644 index 0000000..a7c13b6 --- /dev/null +++ b/Watcher/Services/VersionService.cs @@ -0,0 +1,19 @@ +namespace Watcher.Services; + +public class VersionService : IVersionService +{ + private readonly string _version; + + public VersionService(IConfiguration configuration) + { + // Priorität: Environment Variable > Configuration > Default + _version = Environment.GetEnvironmentVariable("WATCHER_VERSION") + ?? configuration["Application:Version"] + ?? "development"; + } + + public string GetVersion() + { + return _version; + } +} diff --git a/Watcher/Views/Container/Overview.cshtml b/Watcher/Views/Container/Overview.cshtml index 609b675..63694e4 100644 --- a/Watcher/Views/Container/Overview.cshtml +++ b/Watcher/Views/Container/Overview.cshtml @@ -8,32 +8,180 @@ -
-
- - - - - - - - - - - - - @for (int i = 0; i < 3; i++) - { - - - - - - - - - } -
ServiceHostServerStatusSSL/TLSProxyDeployment
testtestOnlinetruetrueDocker
+
+
+

Container & Services

+ @Model.Containers.Count Container
+ @if (!Model.Containers.Any()) + { +
+ Keine Container gefunden. Container werden automatisch von den Agents erkannt. +
+ } + else + { + var groupedContainers = Model.Containers.GroupBy(c => c.Server?.Name ?? "Unbekannt"); + + foreach (var serverGroup in groupedContainers.OrderBy(g => g.Key)) + { +
+
+ @serverGroup.Key + @serverGroup.Count() +
+ +
+ @foreach (var container in serverGroup) + { +
+
+
+
+ + @container.Name +
+ + + @(container.IsRunning ? "Running" : "Stopped") + +
+
+
+
+ ID: + @(container.ContainerId != null && container.ContainerId.Length > 12 ? container.ContainerId.Substring(0, 12) : container.ContainerId) +
+
+ Image: + @container.Image +
+ +
+ + +
+ + + +
+ + +
+ +
+
+
+
+ CPU + --% +
+
+
+
+
+
+
+ RAM + -- MB +
+
+
+
+
+
+
+ Network + + -- MB/s + -- MB/s + +
+
+
+ + + Metriken werden in Echtzeit aktualisiert + +
+
+
+
+
+
+
+ } +
+
+ } + }
+ +@section Scripts { + +} diff --git a/Watcher/Views/Home/_DashboardStats.cshtml b/Watcher/Views/Home/_DashboardStats.cshtml index dc201ff..4b0a81a 100644 --- a/Watcher/Views/Home/_DashboardStats.cshtml +++ b/Watcher/Views/Home/_DashboardStats.cshtml @@ -1,4 +1,3 @@ -@using Microsoft.IdentityModel.Tokens @model Watcher.ViewModels.DashboardViewModel @{ @@ -58,7 +57,7 @@
Systemstatus
- @if (!Model.NetworkStatus.IsNullOrEmpty()) + @if (!string.IsNullOrEmpty(Model.NetworkStatus)) { @if (Model.NetworkStatus == "online") { @@ -81,7 +80,7 @@ } } - @if (!Model.DatabaseStatus.IsNullOrEmpty()) + @if (!string.IsNullOrEmpty(Model.DatabaseStatus)) { @if (Model.DatabaseStatus == "ok") { @@ -139,12 +138,12 @@

Services

    - @foreach (var service in Model.Containers) + @foreach (var container in Model.Containers) {
  • - @service.Name - - @service.Status + @container.Name + + @container.Name
  • } diff --git a/Watcher/Views/Server/Details.cshtml b/Watcher/Views/Server/Details.cshtml index fb903a7..ffb2aa1 100644 --- a/Watcher/Views/Server/Details.cshtml +++ b/Watcher/Views/Server/Details.cshtml @@ -11,50 +11,187 @@
    -
    -
    + +
    +
    @Model.Name
    - - - @(Model.IsOnline ? "Online" : "Offline") - +
    + + + @(Model.IsOnline ? "Online" : "Offline") + + + Bearbeiten + +
    + +
    +
    -
    -
    -
    IP: @Model.IPAddress
    -
    Typ: @Model.Type
    -
    Erstellt: - @Model.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")
    -
    Last-Seen: - @Model.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm")
    -
    -
    -
    CPU: @(Model.CpuType ?? "not found")
    -
    CPU-Kerne: @Model.CpuCores
    -
    GPU: @(Model.GpuType ?? "not found") +
    +
    + +
    +
    + System +
    +
    +
    + + IP-Adresse + + @Model.IPAddress +
    +
    + + Typ + + @Model.Type +
    +
    + + Erstellt + + @Model.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy") +
    +
    + + Last Seen + + + @{ + var timeSinceLastSeen = DateTime.UtcNow - Model.LastSeen; + if (timeSinceLastSeen.TotalMinutes < 1) + { + Gerade eben + } + else if (timeSinceLastSeen.TotalMinutes < 60) + { + vor @((int)timeSinceLastSeen.TotalMinutes) Min + } + else if (timeSinceLastSeen.TotalHours < 24) + { + vor @((int)timeSinceLastSeen.TotalHours) Std + } + else + { + @Model.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm") + } + } + +
    +
    -
    RAM: @(Model.RamSize)
    -
    Disk Space: ...
    -
    -
    - -
    + +
    +
    + Prozessor & Grafik +
    +
    +
    + + CPU + + + @if (!string.IsNullOrWhiteSpace(Model.CpuType)) + { + @Model.CpuType + } + else + { + Unbekannt + } + +
    +
    + + CPU-Kerne + + + @if (Model.CpuCores > 0) + { + @Model.CpuCores + Cores + } + else + { + Unbekannt + } + +
    +
    + + GPU + + + @if (!string.IsNullOrWhiteSpace(Model.GpuType)) + { + @Model.GpuType + } + else + { + Keine / Unbekannt + } + +
    +
    +
    + + +
    +
    + Speicher +
    +
    +
    + + RAM + + + @if (Model.RamSize > 0) + { + var ramGB = Model.RamSize / (1024.0 * 1024.0 * 1024.0); + @($"{ramGB:F1} GB") + } + else + { + Unbekannt + } + +
    +
    +
    + + + @if (!string.IsNullOrWhiteSpace(Model.Description)) + { +
    +
    + Beschreibung +
    +

    @Model.Description

    +
    + } +
    +
    +
    +
    +
    +
    Metriken
    +
    + + + +
    @@ -94,10 +231,13 @@ datasets: [{ label: 'CPU Last (%)', data: [], - backgroundColor: 'rgba(255, 99, 132, 0.2)', + borderColor: 'rgba(13, 202, 240, 1)', + backgroundColor: 'rgba(13, 202, 240, 0.2)', + borderWidth: 2, fill: true, - tension: 0.3, - pointRadius: 3 + tension: 0.4, + pointRadius: 0, + pointHoverRadius: 4 }] }, options: { @@ -110,14 +250,29 @@ title: { display: true, text: 'Auslastung in %' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' } }, x: { title: { display: false, text: 'Zeit' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' + }, + ticks: { + maxTicksLimit: 12, + autoSkip: true } } + }, + plugins: { + legend: { + display: false + } } } }); @@ -129,10 +284,13 @@ datasets: [{ label: 'RAM Last (%)', data: [], - backgroundColor: 'rgba(255, 99, 132, 0.2)', + borderColor: 'rgba(25, 135, 84, 1)', + backgroundColor: 'rgba(25, 135, 84, 0.2)', + borderWidth: 2, fill: true, - tension: 0.3, - pointRadius: 3 + tension: 0.4, + pointRadius: 0, + pointHoverRadius: 4 }] }, options: { @@ -145,14 +303,29 @@ title: { display: true, text: 'Auslastung in %' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' } }, x: { title: { display: false, text: 'Zeit' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' + }, + ticks: { + maxTicksLimit: 12, + autoSkip: true } } + }, + plugins: { + legend: { + display: false + } } } }); @@ -164,10 +337,13 @@ datasets: [{ label: 'GPU Last (%)', data: [], - backgroundColor: 'rgba(255, 99, 132, 0.2)', + borderColor: 'rgba(220, 53, 69, 1)', + backgroundColor: 'rgba(220, 53, 69, 0.2)', + borderWidth: 2, fill: true, - tension: 0.3, - pointRadius: 3 + tension: 0.4, + pointRadius: 0, + pointHoverRadius: 4 }] }, options: { @@ -180,21 +356,38 @@ title: { display: true, text: 'Auslastung in %' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' } }, x: { title: { display: false, text: 'Zeit' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' + }, + ticks: { + maxTicksLimit: 12, + autoSkip: true } } + }, + plugins: { + legend: { + display: false + } } } }); + let currentHours = 1; // Standard: 1 Stunde + async function loadCpuData() { try { - const response = await fetch('/monitoring/cpu-usage?serverId=@Model.Id'); + const response = await fetch(`/monitoring/cpu-usage?serverId=@Model.Id&hours=${currentHours}`); if (!response.ok) { throw new Error(`HTTP-Fehler: ${response.status}`); } @@ -217,7 +410,7 @@ async function loadRamData() { try { - const response = await fetch('/monitoring/ram-usage?serverId=@Model.Id'); + const response = await fetch(`/monitoring/ram-usage?serverId=@Model.Id&hours=${currentHours}`); if (!response.ok) { throw new Error(`HTTP-Fehler: ${response.status}`); } @@ -240,7 +433,7 @@ async function loadGpuData() { try { - const response = await fetch('/monitoring/gpu-usage?serverId=@Model.Id'); + const response = await fetch(`/monitoring/gpu-usage?serverId=@Model.Id&hours=${currentHours}`); if (!response.ok) { throw new Error(`HTTP-Fehler: ${response.status}`); } @@ -261,6 +454,27 @@ } } + // Button-Handler für Zeitbereich-Wechsel + document.querySelectorAll('[data-range]').forEach(button => { + button.addEventListener('click', function() { + // Alle Buttons deaktivieren + document.querySelectorAll('[data-range]').forEach(btn => { + btn.classList.remove('active'); + }); + + // Aktuellen Button aktivieren + this.classList.add('active'); + + // Zeitbereich aktualisieren + currentHours = parseInt(this.getAttribute('data-range')); + + // Daten neu laden + loadCpuData(); + loadRamData(); + loadGpuData(); + }); + }); + // Initiales Laden loadCpuData(); loadRamData(); diff --git a/Watcher/Views/Server/_ServerCard.cshtml b/Watcher/Views/Server/_ServerCard.cshtml index 447a6f2..87ecf7d 100644 --- a/Watcher/Views/Server/_ServerCard.cshtml +++ b/Watcher/Views/Server/_ServerCard.cshtml @@ -1,51 +1,169 @@ @model IEnumerable -
    -
    - @foreach (var s in Model) - { -
    -
    -
    -
    -
    - (#@s.Id) @s.Name -
    - -
    -
    IP: @s.IPAddress
    -
    Typ: @s.Type
    -
    - - - - @(s.IsOnline ? "Online" : "Offline") +
    + @foreach (var server in Model) + { +
    +
    +
    +
    + + @server.Name +
    + + + @(server.IsOnline ? "Online" : "Offline") + +
    +
    + +
    +
    + IP: + @server.IPAddress +
    +
    + Typ: + @server.Type +
    +
    + CPU: + @(server.CpuCores > 0 ? $"{server.CpuCores} Cores" : "N/A") +
    +
    + RAM: + + @if (server.RamSize > 0) + { + var ramGB = server.RamSize / (1024.0 * 1024.0 * 1024.0); + @($"{ramGB:F1} GB") + } + else + { + N/A + } -
    - - - Bearbeiten - - - - Metrics - - -
    - -
    +
    +
    + + @if (server.IsOnline) + { +
    +
    Aktuelle Last
    +
    +
    +
    + CPU + -- +
    +
    +
    +
    +
    +
    +
    + RAM + -- +
    +
    +
    +
    +
    + @if (!string.IsNullOrEmpty(server.GpuType)) + { +
    +
    + GPU + -- +
    +
    +
    +
    +
    + }
    + } + +
    + + Details + + + + Edit + +
    + +
    +
    + +
    +
    + } +
    + + \ No newline at end of file diff --git a/Watcher/Views/Shared/_Layout.cshtml b/Watcher/Views/Shared/_Layout.cshtml index a252560..226fc88 100644 --- a/Watcher/Views/Shared/_Layout.cshtml +++ b/Watcher/Views/Shared/_Layout.cshtml @@ -1,7 +1,10 @@ @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Http +@using Watcher.Services @inject IHttpContextAccessor HttpContextAccessor +@inject IVersionService VersionService +@inject IUpdateCheckStore UpdateCheckStore @{ var pictureUrl = User.FindFirst("picture")?.Value; @@ -98,10 +101,10 @@
    - +
    @User.Identity?.Name
    - Profil ansehen + Profil ansehen
    @@ -110,6 +113,19 @@ { Login } + +
    + + @{ + var statusColor = UpdateCheckStore.IsUpdateAvailable ? "#ffc107" : "#28a745"; + var statusTitle = UpdateCheckStore.IsUpdateAvailable + ? $"Update verfügbar: {UpdateCheckStore.LatestVersion}" + : "System ist aktuell"; + } + + Version: @VersionService.GetVersion() + +
    diff --git a/Watcher/Views/System/Settings.cshtml b/Watcher/Views/System/Settings.cshtml index 23c0ef3..256016d 100644 --- a/Watcher/Views/System/Settings.cshtml +++ b/Watcher/Views/System/Settings.cshtml @@ -12,103 +12,96 @@ -
    +
    +

    + Einstellungen +

    + + +
    +
    +
    + Systemeinformationen +
    +
    +
    + +
    +
    +
    + Version +
    +
    @ServerVersion
    +
    + +
    +
    + Authentifizierung +
    +
    @(ViewBag.IdentityProvider ?? "nicht gefunden")
    +
    + +
    +
    + Datenbank-Engine +
    +
    @(DbEngine ?? "nicht gefunden")
    +
    + + @if (ViewBag.DatabaseSize != null) + { +
    +
    + Datenbankgröße +
    +
    @ViewBag.DatabaseSize
    +
    + } +
    + + + @if (DbEngine == "SQLite" || DbEngine == "Microsoft.EntityFrameworkCore.Sqlite") + { +
    +
    + Datenbank-Backups +
    +
    +
    + +
    +
    + +
    +
    + } + else if (DbEngine == "Microsoft.EntityFrameworkCore.MySQL") + { +
    +
    + MySQL Dump aktuell nicht möglich +
    + } + + + @if (TempData["DumpMessage"] != null) + { +
    + @TempData["DumpMessage"] +
    + } + @if (TempData["DumpError"] != null) + { +
    + @TempData["DumpError"] +
    + } +
    +
    + -
    -

    Systemeinformationen

    - -
    - -
    Watcher Version: @ServerVersion
    - -
    - -
    Authentifizierungsmethode: @(ViewBag.IdentityProvider ?? "nicht gefunden")
    - - -
    - -
    Datenbank-Engine: @(DbEngine ?? "nicht gefunden")
    - - - - @if (DbEngine == "Microsoft.EntityFrameworkCore.Sqlite") - { -
    -
    - -
    - -
    - -
    -
    - - } - else if (DbEngine == "Microsoft.EntityFrameworkCore.MySQL") - { -

    MySQL Dump aktuell nicht möglich

    - } - - - @if (TempData["DumpMessage"] != null) - { -
    - @TempData["DumpMessage"] -
    - } - @if (TempData["DumpError"] != null) - { -
    - @TempData["DumpError"] -
    - } - -
    - -
    -

    Benachrichtungen

    - -

    Registrierte E-Mail Adresse: @(ViewBag.mail ?? "nicht gefunden")

    - - -
    -
    -
    - Benachrichtigungseinstellungen -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - - -
    -
    - -
    - -
    diff --git a/Watcher/Watcher.csproj b/Watcher/Watcher.csproj index 7eb3182..b150629 100644 --- a/Watcher/Watcher.csproj +++ b/Watcher/Watcher.csproj @@ -7,22 +7,16 @@ - - - - - - - - - + + + - + diff --git a/Watcher/appsettings.json b/Watcher/appsettings.json index dff88f0..1cfd965 100644 --- a/Watcher/appsettings.json +++ b/Watcher/appsettings.json @@ -9,22 +9,8 @@ "AllowedHosts": "*", "Database": { - "Provider": "Sqlite", "ConnectionStrings": { - "MySql": "server=0.0.0.0;port=3306;database=db;user=user;password=password;", "Sqlite": "Data Source=./persistence/watcher.db" } - }, - - "Authentication": { - "UseLocal": true, - "PocketIDEnabled": false, - "PocketID": { - "Authority": "https://pocketid.triggermeelmo.com", - "ClientId": "629a5f42-ab02-4905-8311-cc7b64165cc0", - "ClientSecret": "QHUNaRyK2VVYdZVz1cQqv8FEf2qtL6QH", - "CallbackPath": "/signin-oidc", - "ResponseType": "code" - } - } + } } diff --git a/Watcher/wwwroot/css/Login.css b/Watcher/wwwroot/css/Login.css index 4960097..e02fd04 100644 --- a/Watcher/wwwroot/css/Login.css +++ b/Watcher/wwwroot/css/Login.css @@ -19,6 +19,6 @@ } .form-error { - color: #ff6b6b; + color: var(--color-danger); font-size: 0.875rem; } \ No newline at end of file diff --git a/Watcher/wwwroot/css/server-detail.css b/Watcher/wwwroot/css/server-detail.css index f531a0c..49c7e4b 100644 --- a/Watcher/wwwroot/css/server-detail.css +++ b/Watcher/wwwroot/css/server-detail.css @@ -1,20 +1,90 @@ -.info { - margin: 2rem; - margin-top: 3rem; +/* Server Details - Info Card */ +.info-list { + display: flex; + flex-direction: column; + gap: 0.75rem; } -.hardware { - margin: 2rem; - margin-top: 3rem; +.info-item { + display: flex; + flex-direction: column; + gap: 0.25rem; } +.info-label { + font-size: 0.85rem; + font-weight: 500; + color: var(--color-muted) !important; + display: flex; + align-items: center; +} + +.info-value { + font-size: 0.95rem; + font-weight: 400; + padding-left: 1.25rem; + color: var(--color-text, #f9feff) !important; +} + +/* All text within info-value should be visible */ +.info-value, +.info-value *, +.info-value span, +.info-value .text-muted, +.info-value .text-success { + color: var(--color-text, #f9feff) !important; +} + +.info-value .text-muted { + font-style: italic; + font-size: 0.9rem; +} + +/* Section Headers in Info Card */ +.card-body h6.text-muted { + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; + color: var(--color-text) !important; +} + +.card-body h6.text-muted i { + font-size: 1rem; +} + +/* Description and other text-muted paragraphs */ +.card-body p.text-muted { + color: var(--color-text) !important; +} + +/* Graph Container */ .graphcontainer { height: 25rem; width: 100%; - background-color: var(--color-surface); + background-color: var(--color-surface, #212121); + border-radius: 0.375rem; } .graph { width: 100%; height: 22rem; +} + +/* Responsive adjustments */ +@media (max-width: 992px) { + .info-item { + margin-bottom: 0.5rem; + } +} + +@media (max-width: 768px) { + .card-header .d-flex.gap-2 { + flex-wrap: wrap; + } + + .card-header .btn-sm { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + } } \ No newline at end of file diff --git a/Watcher/wwwroot/css/server-overview.css b/Watcher/wwwroot/css/server-overview.css index e69de29..dc7ec97 100644 --- a/Watcher/wwwroot/css/server-overview.css +++ b/Watcher/wwwroot/css/server-overview.css @@ -0,0 +1,106 @@ +/* Server Card Styling */ +.server-card { + transition: transform 0.2s ease, box-shadow 0.2s ease; + border: 1px solid rgba(0, 0, 0, 0.125); + background: var(--color-surface); + color: var(--color-text); +} + +.server-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; +} + +.server-card .card-header { + background-color: var(--color-bg); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.75rem 1rem; +} + +.server-card .card-body { + padding: 1rem; +} + +/* Ensure all text in server-card uses light color */ +.server-card, +.server-card * { + color: var(--color-text); +} + +/* Allow Bootstrap badge colors to work */ +.server-card .badge { + color: white; +} + +/* Allow button colors to work */ +.server-card .btn { + color: inherit; +} + +/* Server Info Rows */ +.server-info { + font-size: 0.9rem; +} + +.info-row { + display: flex; + justify-content: space-between; + padding: 0.4rem 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.info-row:last-child { + border-bottom: none; +} + +.info-label { + color: var(--color-muted); + font-weight: 500; +} + +.info-value { + word-break: break-all; +} + +/* Current Metrics Section */ +.current-metrics h6 { + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.metric-bar-item { + margin-bottom: 0.5rem; +} + +.metric-bar-item:last-child { + margin-bottom: 0; +} + +.progress { + background-color: rgba(255, 255, 255, 0.1); +} + +/* Action Buttons */ +.action-buttons .btn { + font-size: 0.85rem; + padding: 0.4rem 0.6rem; +} + +/* Responsive Adjustments */ +@media (max-width: 768px) { + .action-buttons { + flex-direction: column; + } + + .action-buttons .btn { + width: 100%; + } +} + +/* Badge Styling */ +.badge { + font-size: 0.75rem; + padding: 0.35em 0.65em; +} diff --git a/Watcher/wwwroot/css/services-overview.css b/Watcher/wwwroot/css/services-overview.css index f2a983e..c31ebb4 100644 --- a/Watcher/wwwroot/css/services-overview.css +++ b/Watcher/wwwroot/css/services-overview.css @@ -1,12 +1,124 @@ -.ServiceList { - width: 80%; +/* Container Card Styling */ +.container-card { + transition: transform 0.2s ease, box-shadow 0.2s ease; + border: 1px solid rgba(0, 0, 0, 0.125); + background: var(--color-surface); } -.ServiceRow { - border-style: solid; - border-color: var(--color-text); +.container-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; } -.ServiceEntry { - text-decoration: none; +.container-card .card-header { + background-color: var(--color-bg); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.75rem 1rem; +} + +.container-card .card-body { + padding: 1rem; +} + +/* Container Info Rows */ +.container-info { + font-size: 0.9rem; +} + +.info-row { + display: flex; + justify-content: space-between; + padding: 0.4rem 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +.info-row:last-child { + border-bottom: none; +} + +.info-label { + color: var(--color-muted); + font-weight: 500; +} + +.info-value { + color: var(--color-text, #f9feff); + word-break: break-all; +} + +.info-value a { + color: var(--color-text); + transition: color 0.2s ease; +} + +.info-value a:hover { + color: var(--color-primary); +} + +/* Action Buttons */ +.action-buttons .btn { + font-size: 0.85rem; + padding: 0.4rem 0.6rem; +} + +/* Metrics Panel */ +.metrics-panel { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease-out; +} + +.metrics-panel.show { + max-height: 500px; + transition: max-height 0.4s ease-in; +} + +.metrics-content { + background-color: var(--color-bg) !important; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.metric-item { + margin-bottom: 0.5rem; +} + +.metric-item:last-child { + margin-bottom: 0; +} + +.progress { + background-color: rgba(0, 0, 0, 0.1); +} + +/* Toggle Button */ +.toggle-metrics { + transition: all 0.2s ease; +} + +.toggle-metrics:hover { + transform: scale(1.02); +} + +/* Responsive Adjustments */ +@media (max-width: 768px) { + .action-buttons { + flex-direction: column; + } + + .action-buttons .btn { + width: 100%; + } +} + +/* Badge Styling */ +.badge { + font-size: 0.75rem; + padding: 0.35em 0.65em; +} + +/* Server Group Header */ +h5.text-muted { + font-weight: 600; + border-bottom: 2px solid var(--color-accent); + padding-bottom: 0.5rem; } \ No newline at end of file diff --git a/Watcher/wwwroot/css/settings.css b/Watcher/wwwroot/css/settings.css index 871de0b..c870171 100644 --- a/Watcher/wwwroot/css/settings.css +++ b/Watcher/wwwroot/css/settings.css @@ -1,24 +1,158 @@ -.Settingscontainer { +/* Settings Card Styling - gleicher Stil wie Server Cards */ +.settings-card { + background: var(--color-surface); + border: 1px solid rgba(255, 255, 255, 0.1); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.settings-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; +} + +.settings-card .card-header { + background-color: var(--color-bg); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.75rem 1rem; +} + +.settings-card .card-header h5 { + color: var(--color-text); + font-weight: 600; + font-size: 1rem; +} + +.settings-card .card-body { + padding: 1rem; +} + +/* Info Table Layout - ähnlich wie Server Info Rows */ +.info-table { display: flex; + flex-direction: column; + gap: 0; +} + +.info-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + transition: background-color 0.2s ease; +} + +.info-row:last-child { + border-bottom: none; +} + +.info-row:hover { + background-color: rgba(255, 255, 255, 0.02); +} + +.info-label { + color: var(--color-muted); + font-weight: 500; + font-size: 0.9rem; + display: flex; + align-items: center; +} + +.info-label i { + color: var(--color-accent); +} + +.info-value { + color: var(--color-text); + font-weight: 600; + font-size: 0.9rem; + text-align: right; +} + +/* Subsection Headers */ +.settings-card h6 { + color: var(--color-text); + font-weight: 600; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Backup Buttons */ +.backup-buttons { + display: flex; + gap: 0.75rem; 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; +.backup-buttons form { + flex: 1; + min-width: 200px; } -.btn-db { - background-color: var(--color-primary); - border: none; +.backup-buttons .btn { + width: 100%; } -.btn-db:hover { - background-color: var(--color-accent); - border: none; +/* Button Styling */ +.settings-card .btn { + font-weight: 500; + font-size: 0.85rem; + padding: 0.5rem 0.75rem; + transition: all 0.2s ease; } +.settings-card .btn-outline-primary { + border-color: var(--color-accent) !important; + color: var(--color-accent) !important; + background-color: transparent !important; +} + +.settings-card .btn-outline-primary:hover { + background-color: var(--color-accent) !important; + border-color: var(--color-accent) !important; + color: white !important; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + +.settings-card .btn-outline-primary:active, +.settings-card .btn-outline-primary:focus, +.settings-card .btn-outline-primary:focus-visible { + background-color: var(--color-accent) !important; + border-color: var(--color-accent) !important; + color: white !important; +} + +/* HR Styling */ +.settings-card hr { + border-top: 1px solid rgba(255, 255, 255, 0.1); + margin: 1.5rem 0; +} + +/* Alert Styling */ +.alert { + border-radius: 0.375rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .info-row { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding: 0.75rem; + } + + .info-value { + text-align: left; + } + + .backup-buttons { + flex-direction: column; + } + + .backup-buttons form { + width: 100%; + } +} diff --git a/Watcher/wwwroot/css/site.css b/Watcher/wwwroot/css/site.css index 071d263..304ea49 100644 --- a/Watcher/wwwroot/css/site.css +++ b/Watcher/wwwroot/css/site.css @@ -6,6 +6,7 @@ --color-text: #f9feff; --color-muted: #c0c0c0; --color-success: #14a44d; + --color-success-hover: #0f8c3c; --color-danger: #ff6b6b; } @@ -54,9 +55,30 @@ a { } .btn-pocketid:hover { - background-color: #0f8c3c; + background-color: var(--color-success-hover); } hr { border-top: 1px solid var(--color-accent); } + +/* Bootstrap Overrides für Dark Theme */ +.text-muted { + color: var(--color-muted) !important; +} + +.bg-light { + background-color: var(--color-surface) !important; +} + +.text-text { + color: var(--color-text) !important; +} + +.text-primary-emphasis { + color: var(--color-primary) !important; +} + +.border-secondary { + border-color: rgba(255, 255, 255, 0.2) !important; +} diff --git a/Watcher/wwwroot/css/user-info.css b/Watcher/wwwroot/css/user-info.css index 89c5118..ebbca38 100644 --- a/Watcher/wwwroot/css/user-info.css +++ b/Watcher/wwwroot/css/user-info.css @@ -1,6 +1,5 @@ .table { - color: red; - + color: var(--color-text); } .picture { diff --git a/docker-compose.yaml b/docker-compose.yaml index 6eacc9c..347348f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,6 @@ services: watcher: - image: git.triggermeelmo.com/watcher/watcher-server:v0.1.0 + image: git.triggermeelmo.com/watcher/watcher-server:${IMAGE_VERSION:-latest} container_name: watcher deploy: resources: @@ -8,6 +8,17 @@ services: memory: 200M restart: unless-stopped env_file: .env + environment: + # Application Version (wird aus Image-Tag übernommen) + - WATCHER_VERSION=${IMAGE_VERSION:-latest} + # Update Check + - UPDATE_CHECK_ENABLED=true + - UPDATE_CHECK_INTERVAL_HOURS=24 + - UPDATE_CHECK_REPOSITORY_URL=https://git.triggermeelmo.com/api/v1/repos/Watcher/watcher/releases/latest + # Data Retention Policy + - METRIC_RETENTION_DAYS=30 + - METRIC_CLEANUP_INTERVAL_HOURS=24 + - METRIC_CLEANUP_ENABLED=true ports: - "5000:5000" volumes: