diff --git a/README.md b/README.md index 87805b7..754dae6 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,17 @@
+ +

+ Gitea Online + Debian 12 + Runner Active + Last Sync +

+ + +
+ [![C#](https://img.shields.io/badge/C%23-239120?style=for-the-badge&logo=csharp&logoColor=white)](https://dotnet.microsoft.com/) [![.NET 10](https://img.shields.io/badge/.NET_10-512BD4?style=for-the-badge&logo=dotnet&logoColor=white)](https://dotnet.microsoft.com/) [![Avalonia UI](https://img.shields.io/badge/Avalonia_UI-7e3f98?style=for-the-badge&logo=dotnet&logoColor=white)](https://avaloniaui.net/) @@ -40,7 +51,7 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
-## πŸ“Š Live AktivitΓ€ts- & Performance-Dashboards +## πŸ“Š Live AktivitΓ€ts- & Multi-Tier Dashboards
@@ -96,20 +107,27 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus ---- +
-## πŸ› οΈ AusgewΓ€hlte Eigenprojekte +
+

⚑ Tab 3: Live Commit-Radar & AktivitÀts-Feed

-| Projekt | Stack | Beschreibung | -| :--- | :---: | :--- | -| πŸ“ **Markdown Editor Pro** | `Avalonia UI` β€’ `C#` | Desktop-Editor mit Live-Rendering, Math-Support und Theme-Engine. | -| πŸ’§ **CloudDrop** | `ASP.NET Core` β€’ `Docker` | Self-hosted File-Sharing Service fΓΌr schnelle Uploads & temporΓ€re Links. | -| πŸ’³ **KasseApp** | `C#` β€’ `SQLite` β€’ `Avalonia` | Modulares Kassensystem mit Offline-Synchronisation fΓΌr Events & Vereine. | -| πŸ₯€ **DrinkReminder** | `C#` β€’ `Avalonia UI` | Schicker Desktop- & Mobile-Helfer fΓΌr gesunde Hydration im Entwickleralltag. | +> *Echtzeit-Feed der letzten Aktionen und Commits ΓΌber alle privaten & Team-Repositories.* ---- + +- ⚑ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 2 Minuten* +- ⚑ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 10 Minuten* +- ⚑ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 18 Minuten* +- ⚑ **Initial profile setup** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 19 Minuten* +- ✨ **Repository neu erstellt** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 55 Minuten* + -## πŸ›‘οΈ Homelab & Server-Infrastruktur +
+ +
+ +
+

πŸ›‘οΈ Tab 4: Homelab, Server-Infrastruktur & Telemetrie

``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” @@ -130,11 +148,26 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -- **Betriebssystem:** Debian GNU/Linux 12 (Bookworm) -- **Container-Runtime:** Docker Engine & Docker Compose -- **Reverse Proxy:** Nginx mit automatischen Let's Encrypt TLS-Zertifikaten -- **Security:** UFW Firewall, fail2ban (SSH & HTTP Brute-Force Schutz) -- **Monitoring & CI/CD:** Gitea Actions mit dediziertem Docker `act_runner` +| Service | Host / Umgebung | Funktion | Status | +| :--- | :--- | :--- | :---: | +| 🦊 **Gitea Service** | `git.webcore-consulting.dev` | Versionsverwaltung, API & Git LFS | `🟒 ONLINE` | +| ⚑ **Gitea Act-Runner** | Docker `catthehacker/ubuntu:act` | Autonome CI/CD-Pipeline (.NET 10) | `🟒 AKTIV` | +| βœ‰οΈ **Mailcow Suite** | Docker Stack / Debian 12 | Enterprise Mail (DKIM, SPF, DMARC) | `🟒 ONLINE` | +| πŸ›‘οΈ **Nginx Proxy** | Reverse Proxy Host | SSL/TLS 1.3 Offloading & HSTS | `🟒 AKTIV` | +| πŸ”’ **Security Wall** | UFW Firewall & fail2ban | SSH & HTTP Brute-Force Abwehr | `🟒 SCHARF` | + +
+ +--- + +## πŸ› οΈ AusgewΓ€hlte Eigenprojekte + +| Projekt | Stack | Beschreibung | +| :--- | :---: | :--- | +| πŸ“ **Markdown Editor Pro** | `Avalonia UI` β€’ `C#` | Desktop-Editor mit Live-Rendering, Math-Support und Theme-Engine. | +| πŸ’§ **CloudDrop** | `ASP.NET Core` β€’ `Docker` | Self-hosted File-Sharing Service fΓΌr schnelle Uploads & temporΓ€re Links. | +| πŸ’³ **KasseApp** | `C#` β€’ `SQLite` β€’ `Avalonia` | Modulares Kassensystem mit Offline-Synchronisation fΓΌr Events & Vereine. | +| πŸ₯€ **DrinkReminder** | `C#` β€’ `Avalonia UI` | Schicker Desktop- & Mobile-Helfer fΓΌr gesunde Hydration im Entwickleralltag. | --- diff --git a/heatmap.svg b/heatmap.svg index d30fe62..66bbbf0 100644 --- a/heatmap.svg +++ b/heatmap.svg @@ -74,7 +74,7 @@ 🦊 NickiCloud β€’ PersΓΆnliche AktivitΓ€t - 24 Commits im letzten Jahr + 25 Commits im letzten Jahr Sep @@ -1173,7 +1173,7 @@ 01.09.2026: 9 Commits - 02.09.2026: 8 Commits + 02.09.2026: 9 Commits 03.09.2026: 0 Commits @@ -1192,7 +1192,7 @@ πŸ“¦ - 24 + 25 Jahres-BeitrΓ€ge @@ -1209,11 +1209,11 @@ - - C#: 57,0% + + C#: 57,3% - - HTML: 39,7% + + HTML: 39,4% CSS: 2,3% @@ -1228,9 +1228,9 @@ - C# 57,0% + C# 57,3% - HTML 39,7% + HTML 39,4% CSS 2,3% diff --git a/src/ProfileManager/Models/ActivityFeed.cs b/src/ProfileManager/Models/ActivityFeed.cs new file mode 100644 index 0000000..4905743 --- /dev/null +++ b/src/ProfileManager/Models/ActivityFeed.cs @@ -0,0 +1,82 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ProfileManager.Models; + +public sealed class ActivityCommit +{ + [JsonPropertyName("Sha1")] + public string Sha1 { get; set; } = string.Empty; + + [JsonPropertyName("Message")] + public string Message { get; set; } = string.Empty; + + [JsonPropertyName("AuthorName")] + public string AuthorName { get; set; } = string.Empty; + + [JsonPropertyName("Timestamp")] + public string Timestamp { get; set; } = string.Empty; +} + +public sealed class ActivityContent +{ + [JsonPropertyName("Commits")] + public List? Commits { get; set; } + + [JsonPropertyName("HeadCommit")] + public ActivityCommit? HeadCommit { get; set; } +} + +public sealed class ActivityItem +{ + [JsonPropertyName("id")] + public long Id { get; set; } + + [JsonPropertyName("op_type")] + public string OpType { get; set; } = string.Empty; + + [JsonPropertyName("repo")] + public GiteaRepo? Repo { get; set; } + + [JsonPropertyName("ref_name")] + public string? RefName { get; set; } + + [JsonPropertyName("created")] + public DateTime Created { get; set; } + + [JsonPropertyName("content")] + public string? RawContent { get; set; } + + [JsonIgnore] + public string DisplayMessage + { + get + { + if (!string.IsNullOrWhiteSpace(RawContent)) + { + try + { + var content = JsonSerializer.Deserialize(RawContent); + var commit = content?.HeadCommit ?? content?.Commits?.FirstOrDefault(); + if (commit != null && !string.IsNullOrWhiteSpace(commit.Message)) + { + var firstLine = commit.Message.Trim().Split('\n')[0].Trim(); + return firstLine; + } + } + catch + { + // Ignore parse error + } + } + + return OpType switch + { + "commit_repo" => "Neuer Commit gepusht", + "create_repo" => "Repository neu erstellt", + "create_branch" => "Branch angelegt", + _ => "AktivitΓ€t auf Repository" + }; + } + } +} diff --git a/src/ProfileManager/Models/ProfileStats.cs b/src/ProfileManager/Models/ProfileStats.cs index a70009d..7341413 100644 --- a/src/ProfileManager/Models/ProfileStats.cs +++ b/src/ProfileManager/Models/ProfileStats.cs @@ -53,4 +53,5 @@ public sealed class ProfileStats public required List OverallLanguages { get; init; } public required List PersonalRepos { get; init; } public required TeamStats? TeamStats { get; init; } + public required List RecentActivities { get; init; } } diff --git a/src/ProfileManager/Program.cs b/src/ProfileManager/Program.cs index 925f8eb..d53759f 100644 --- a/src/ProfileManager/Program.cs +++ b/src/ProfileManager/Program.cs @@ -38,16 +38,18 @@ internal static class Program { var client = new GiteaApiClient(apiUrl, string.IsNullOrWhiteSpace(token) ? null : token); - // Step 1: Parallel retrieval of Heatmap, Accessible Repos, and Organizations + // Step 1: Parallel retrieval of Heatmap, Accessible Repos, Organizations, and Activity Feed var heatmapTask = client.GetHeatmapAsync(username); var reposTask = client.GetAllAccessibleReposAsync(username); var orgsTask = client.GetUserOrganizationsAsync(username); + var activitiesTask = client.GetRecentActivitiesAsync(username); - await Task.WhenAll(heatmapTask, reposTask, orgsTask); + await Task.WhenAll(heatmapTask, reposTask, orgsTask, activitiesTask); var heatmap = await heatmapTask; var repos = await reposTask; var orgs = await orgsTask; + var activities = await activitiesTask; // Step 2: Fetch detailed language byte counts per repository Console.WriteLine("[API] Fetching byte-level language breakdown for all repositories..."); @@ -59,8 +61,8 @@ internal static class Program await Task.WhenAll(langTasks); // Step 3: Calculate multi-tier statistics - Console.WriteLine("[Stats] Calculating Personal, Team & Byte metrics..."); - var stats = StatsCalculator.Calculate(heatmap, repos, orgs, username); + Console.WriteLine("[Stats] Calculating Personal, Team, Byte & Activity metrics..."); + var stats = StatsCalculator.Calculate(heatmap, repos, orgs, activities, username); Console.WriteLine($"[Stats] 365d Contributions: {stats.TotalContributions365}"); Console.WriteLine($"[Stats] Current Streak : {stats.CurrentStreak} days"); diff --git a/src/ProfileManager/Services/GiteaApiClient.cs b/src/ProfileManager/Services/GiteaApiClient.cs index 58a5b46..4941bdc 100644 --- a/src/ProfileManager/Services/GiteaApiClient.cs +++ b/src/ProfileManager/Services/GiteaApiClient.cs @@ -176,6 +176,35 @@ public sealed class GiteaApiClient return []; } + public async Task> GetRecentActivitiesAsync(string username, CancellationToken cancellationToken = default) + { + var url = $"{_baseUrl}/users/{Uri.EscapeDataString(username)}/activities/feeds?limit=15"; + Console.WriteLine($"[API] Fetching recent activities from: {url}"); + + try + { + using var response = await _httpClient.GetAsync(url, cancellationToken); + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(cancellationToken); + var items = JsonSerializer.Deserialize>(json, JsonOptions); + if (items != null && items.Count > 0) + { + Console.WriteLine($"[API] Retrieved {items.Count} recent activity feed items."); + return items; + } + } + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"[API-WARN] Activity feed error: {ex.Message}."); + Console.ResetColor(); + } + + return []; + } + private static List GenerateMockHeatmap() { var items = new List(); diff --git a/src/ProfileManager/Services/ReadmeInjector.cs b/src/ProfileManager/Services/ReadmeInjector.cs index 9ff9c66..8c2bb1a 100644 --- a/src/ProfileManager/Services/ReadmeInjector.cs +++ b/src/ProfileManager/Services/ReadmeInjector.cs @@ -14,6 +14,12 @@ public static class ReadmeInjector private const string StartLangMarker = ""; private const string EndLangMarker = ""; + private const string StartActivityMarker = ""; + private const string EndActivityMarker = ""; + + private const string StartTelemetryMarker = ""; + private const string EndTelemetryMarker = ""; + public static bool InjectAll(string readmePath, ProfileStats stats) { if (!File.Exists(readmePath)) @@ -95,9 +101,47 @@ public static class ReadmeInjector } }); + // 4. Inject Live Activity Stream + content = ReplaceSection(content, StartActivityMarker, EndActivityMarker, sb => + { + if (stats.RecentActivities.Count == 0) + { + sb.AppendLine("*Aktuell keine kΓΌrzlichen AktivitΓ€ten im Feed.*"); + } + else + { + foreach (var act in stats.RecentActivities.Take(5)) + { + var repoName = act.Repo?.FullName ?? "Repository"; + var repoUrl = act.Repo?.HtmlUrl ?? $"https://git.webcore-consulting.dev/{repoName}"; + var relTime = FormatRelativeTime(act.Created); + var icon = act.OpType switch + { + "commit_repo" => "⚑", + "create_repo" => "✨", + _ => "πŸ“Œ" + }; + + sb.AppendLine($"- {icon} **{act.DisplayMessage}** auf [`{repoName}`]({repoUrl}) β€’ πŸ•’ *{relTime}*"); + } + } + }); + + // 5. Inject Telemetry Header + content = ReplaceSection(content, StartTelemetryMarker, EndTelemetryMarker, sb => + { + var now = DateTime.UtcNow.ToString("dd.MM.yyyy HH:mm"); + sb.AppendLine($"

"); + sb.AppendLine($" \"Gitea"); + sb.AppendLine($" \"Debian"); + sb.AppendLine($" \"Runner"); + sb.AppendLine($" \"Last"); + sb.AppendLine($"

"); + }); + var utf8WithoutBom = new UTF8Encoding(false); File.WriteAllText(readmePath, content, utf8WithoutBom); - Console.WriteLine($"[README] Successfully updated all dynamic sections in {readmePath}"); + Console.WriteLine($"[README] Successfully updated all 5 dynamic sections in {readmePath}"); return true; } @@ -118,4 +162,15 @@ public static class ReadmeInjector return source.Substring(0, startIndex) + sb + source.Substring(endIndex + endMarker.Length); } + + private static string FormatRelativeTime(DateTime dateTime) + { + var diff = DateTime.UtcNow - dateTime.ToUniversalTime(); + if (diff.TotalMinutes < 1) return "gerade eben"; + if (diff.TotalMinutes < 60) return $"vor {(int)diff.TotalMinutes} Minuten"; + if (diff.TotalHours < 24) return $"vor {(int)diff.TotalHours} Stunden"; + if (diff.TotalDays < 2) return "gestern"; + if (diff.TotalDays < 30) return $"vor {(int)diff.TotalDays} Tagen"; + return dateTime.ToString("dd.MM.yyyy"); + } } diff --git a/src/ProfileManager/Services/StatsCalculator.cs b/src/ProfileManager/Services/StatsCalculator.cs index 35338de..0b6ab9f 100644 --- a/src/ProfileManager/Services/StatsCalculator.cs +++ b/src/ProfileManager/Services/StatsCalculator.cs @@ -26,6 +26,7 @@ public static class StatsCalculator List heatmapItems, List allRepos, List orgs, + List recentActivities, string personalUsername = "NickiCloud", DateOnly? referenceDate = null) { @@ -162,7 +163,8 @@ public static class StatsCalculator Days = days, OverallLanguages = overallLanguages, PersonalRepos = personalRepos, - TeamStats = teamStats + TeamStats = teamStats, + RecentActivities = recentActivities }; }