feat: add live commit radar, activity feed and homelab telemetry [skip ci]
This commit is contained in:
parent
06e49cc0fc
commit
3ba3863f54
65
README.md
65
README.md
@ -7,6 +7,17 @@
|
||||
|
||||
<br/>
|
||||
|
||||
<!-- START_SYS_TELEMETRY -->
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Gitea_Server-Online_%E2%97%8F-2ea44f?style=flat-square&logo=gitea&logoColor=white" alt="Gitea Online" />
|
||||
<img src="https://img.shields.io/badge/Homelab-Debian_12_Bookworm-A81D33?style=flat-square&logo=debian&logoColor=white" alt="Debian 12" />
|
||||
<img src="https://img.shields.io/badge/CI%2FCD-Gitea_Act_Runner-609926?style=flat-square&logo=docker&logoColor=white" alt="Runner Active" />
|
||||
<img src="https://img.shields.io/badge/Telemetry-Zuletzt_aktualisiert_02.09.2026%2019%3A12_UTC-7e3f98?style=flat-square" alt="Last Sync" />
|
||||
</p>
|
||||
<!-- END_SYS_TELEMETRY -->
|
||||
|
||||
<br/>
|
||||
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://avaloniaui.net/)
|
||||
@ -40,7 +51,7 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 📊 Live Aktivitäts- & Performance-Dashboards
|
||||
## 📊 Live Aktivitäts- & Multi-Tier Dashboards
|
||||
|
||||
</div>
|
||||
|
||||
@ -96,20 +107,27 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
<br/>
|
||||
|
||||
## 🛠️ Ausgewählte Eigenprojekte
|
||||
<details open>
|
||||
<summary><h3>⚡ Tab 3: Live Commit-Radar & Aktivitäts-Feed</h3></summary>
|
||||
|
||||
| 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.*
|
||||
|
||||
---
|
||||
<!-- START_RECENT_ACTIVITY -->
|
||||
- ⚡ **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*
|
||||
<!-- END_RECENT_ACTIVITY -->
|
||||
|
||||
## 🛡️ Homelab & Server-Infrastruktur
|
||||
</details>
|
||||
|
||||
<br/>
|
||||
|
||||
<details open>
|
||||
<summary><h3>🛡️ Tab 4: Homelab, Server-Infrastruktur & Telemetrie</h3></summary>
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
@ -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` |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 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. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
18
heatmap.svg
18
heatmap.svg
@ -74,7 +74,7 @@
|
||||
<text class="title" x="0" y="14">🦊 NickiCloud <tspan font-weight="400" fill="var(--accent)">•</tspan> <tspan font-weight="500" font-size="12" fill="var(--text-sub)">Persönliche Aktivität</tspan></text>
|
||||
<rect x="510" y="0" width="190" height="22" rx="11" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" />
|
||||
<circle cx="522" cy="11" r="4" fill="var(--accent)" />
|
||||
<text class="subtitle" x="532" y="15" font-weight="600">24 Commits im letzten Jahr</text>
|
||||
<text class="subtitle" x="532" y="15" font-weight="600">25 Commits im letzten Jahr</text>
|
||||
</g>
|
||||
<!-- Month Labels -->
|
||||
<text class="lbl-month" x="34" y="54">Sep</text>
|
||||
@ -1173,7 +1173,7 @@
|
||||
<title>01.09.2026: 9 Commits</title>
|
||||
</rect>
|
||||
<rect class="cell lvl-3" x="697" y="90" width="10" height="10" rx="2" style="animation-delay: 612ms;">
|
||||
<title>02.09.2026: 8 Commits</title>
|
||||
<title>02.09.2026: 9 Commits</title>
|
||||
</rect>
|
||||
<rect class="cell lvl-0" x="697" y="103" width="10" height="10" rx="2" style="animation-delay: 612ms;">
|
||||
<title>03.09.2026: 0 Commits</title>
|
||||
@ -1192,7 +1192,7 @@
|
||||
<g transform="translate(34, 166)">
|
||||
<rect width="216" height="36" rx="8" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" />
|
||||
<text x="12" y="27" font-size="16">📦</text>
|
||||
<text class="stat-val" x="40" y="20">24</text>
|
||||
<text class="stat-val" x="40" y="20">25</text>
|
||||
<text class="stat-lbl" x="40" y="33">Jahres-Beiträge</text>
|
||||
</g>
|
||||
<g transform="translate(264, 166)">
|
||||
@ -1209,11 +1209,11 @@
|
||||
</g>
|
||||
<!-- Top Languages Bar -->
|
||||
<g clip-path="url(#langBarClip)">
|
||||
<rect x="34.0" y="215" width="385.3" height="8" fill="#a855f7">
|
||||
<title>C#: 57,0%</title>
|
||||
<rect x="34.0" y="215" width="387.3" height="8" fill="#a855f7">
|
||||
<title>C#: 57,3%</title>
|
||||
</rect>
|
||||
<rect x="419.3" y="215" width="268.4" height="8" fill="#e34c26">
|
||||
<title>HTML: 39,7%</title>
|
||||
<rect x="421.3" y="215" width="266.3" height="8" fill="#e34c26">
|
||||
<title>HTML: 39,4%</title>
|
||||
</rect>
|
||||
<rect x="687.7" y="215" width="15.5" height="8" fill="#563d7c">
|
||||
<title>CSS: 2,3%</title>
|
||||
@ -1228,9 +1228,9 @@
|
||||
<!-- Language Legend -->
|
||||
<g transform="translate(34, 238)">
|
||||
<circle cx="4" cy="4" r="4" fill="#a855f7" />
|
||||
<text class="lang-lbl" x="13" y="7">C# 57,0%</text>
|
||||
<text class="lang-lbl" x="13" y="7">C# 57,3%</text>
|
||||
<circle cx="89" cy="4" r="4" fill="#e34c26" />
|
||||
<text class="lang-lbl" x="98" y="7">HTML 39,7%</text>
|
||||
<text class="lang-lbl" x="98" y="7">HTML 39,4%</text>
|
||||
<circle cx="188" cy="4" r="4" fill="#563d7c" />
|
||||
<text class="lang-lbl" x="197" y="7">CSS 2,3%</text>
|
||||
<circle cx="273" cy="4" r="4" fill="#f1e05a" />
|
||||
|
||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
82
src/ProfileManager/Models/ActivityFeed.cs
Normal file
82
src/ProfileManager/Models/ActivityFeed.cs
Normal file
@ -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<ActivityCommit>? 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<ActivityContent>(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"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,4 +53,5 @@ public sealed class ProfileStats
|
||||
public required List<LanguageStat> OverallLanguages { get; init; }
|
||||
public required List<GiteaRepo> PersonalRepos { get; init; }
|
||||
public required TeamStats? TeamStats { get; init; }
|
||||
public required List<ActivityItem> RecentActivities { get; init; }
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -176,6 +176,35 @@ public sealed class GiteaApiClient
|
||||
return [];
|
||||
}
|
||||
|
||||
public async Task<List<ActivityItem>> 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<List<ActivityItem>>(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<HeatmapItem> GenerateMockHeatmap()
|
||||
{
|
||||
var items = new List<HeatmapItem>();
|
||||
|
||||
@ -14,6 +14,12 @@ public static class ReadmeInjector
|
||||
private const string StartLangMarker = "<!-- START_LANGUAGE_METRICS -->";
|
||||
private const string EndLangMarker = "<!-- END_LANGUAGE_METRICS -->";
|
||||
|
||||
private const string StartActivityMarker = "<!-- START_RECENT_ACTIVITY -->";
|
||||
private const string EndActivityMarker = "<!-- END_RECENT_ACTIVITY -->";
|
||||
|
||||
private const string StartTelemetryMarker = "<!-- START_SYS_TELEMETRY -->";
|
||||
private const string EndTelemetryMarker = "<!-- END_SYS_TELEMETRY -->";
|
||||
|
||||
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($"<p align=\"center\">");
|
||||
sb.AppendLine($" <img src=\"https://img.shields.io/badge/Gitea_Server-Online_%E2%97%8F-2ea44f?style=flat-square&logo=gitea&logoColor=white\" alt=\"Gitea Online\" />");
|
||||
sb.AppendLine($" <img src=\"https://img.shields.io/badge/Homelab-Debian_12_Bookworm-A81D33?style=flat-square&logo=debian&logoColor=white\" alt=\"Debian 12\" />");
|
||||
sb.AppendLine($" <img src=\"https://img.shields.io/badge/CI%2FCD-Gitea_Act_Runner-609926?style=flat-square&logo=docker&logoColor=white\" alt=\"Runner Active\" />");
|
||||
sb.AppendLine($" <img src=\"https://img.shields.io/badge/Telemetry-Zuletzt_aktualisiert_{Uri.EscapeDataString(now)}_UTC-7e3f98?style=flat-square\" alt=\"Last Sync\" />");
|
||||
sb.AppendLine($"</p>");
|
||||
});
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ public static class StatsCalculator
|
||||
List<HeatmapItem> heatmapItems,
|
||||
List<GiteaRepo> allRepos,
|
||||
List<GiteaOrganization> orgs,
|
||||
List<ActivityItem> 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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user