Unify telemetry storage and add repo filtering

Refactor telemetry backend to store all telemetry in a single collection and add repo_source-based filtering.

Key changes:
- Added detect_repo_source() in misc/api.func to auto-detect/export REPO_SOURCE (ProxmoxVE/ProxmoxVED/external) when scripts are sourced.
- Consolidated PocketBase collections into a single default collection (_telemetry_data) across service, migration, and scripts; updated defaults in migrate.go, migration.go, migrate.sh and migration shell scripts.
- Simplified PBClient to use one targetColl and removed collection resolution logic; updated create/update/find/fetch functions to use targetColl.
- Introduced repo_source field (values: "ProxmoxVE", "ProxmoxVED", "external") on telemetry records and telemetry payloads; updated validation and logging.
- Added repo filtering to dashboard endpoints, FetchDashboardData and FetchRecordsPaginated, plus a repo selector in the dashboard UI; default filter is ProxmoxVE (production), with an "all" option.
- Adjusted API handlers and callers to pass repo filters and include repo_source when upserting telemetry.
- Misc: updated comments, error messages, and logging to reflect the new model; added telemetry-service.exe binary.

Purpose: simplify data model (single collection), make telemetry attributable to repository sources, and enable dashboard filtering by repo/source.
This commit is contained in:
CanbiZ (MickLesk)
2026-02-11 15:49:41 +01:00
parent 6529949f69
commit dc47c1c1c3
9 changed files with 165 additions and 77 deletions

View File

@@ -104,20 +104,31 @@ type AddonCount struct {
}
// FetchDashboardData retrieves aggregated data from PocketBase
func (p *PBClient) FetchDashboardData(ctx context.Context, days int) (*DashboardData, error) {
// repoSource filters by repo_source field ("ProxmoxVE", "ProxmoxVED", "external", or "" for all)
func (p *PBClient) FetchDashboardData(ctx context.Context, days int, repoSource string) (*DashboardData, error) {
if err := p.ensureAuth(ctx); err != nil {
return nil, err
}
data := &DashboardData{}
// Calculate date filter (days=0 means all entries)
var filter string
// Build filter parts
var filterParts []string
// Date filter (days=0 means all entries)
if days > 0 {
since := time.Now().AddDate(0, 0, -days).Format("2006-01-02 00:00:00")
filter = url.QueryEscape(fmt.Sprintf("created >= '%s'", since))
} else {
filter = "" // No filter = all entries
filterParts = append(filterParts, fmt.Sprintf("created >= '%s'", since))
}
// Repo source filter
if repoSource != "" {
filterParts = append(filterParts, fmt.Sprintf("repo_source = '%s'", repoSource))
}
var filter string
if len(filterParts) > 0 {
filter = url.QueryEscape(strings.Join(filterParts, " && "))
}
// Fetch all records for the period
@@ -306,10 +317,10 @@ func (p *PBClient) fetchRecords(ctx context.Context, filter string) ([]Telemetry
var url string
if filter != "" {
url = fmt.Sprintf("%s/api/collections/%s/records?filter=%s&sort=-created&page=%d&perPage=%d",
p.baseURL, p.devColl, filter, page, perPage)
p.baseURL, p.targetColl, filter, page, perPage)
} else {
url = fmt.Sprintf("%s/api/collections/%s/records?sort=-created&page=%d&perPage=%d",
p.baseURL, p.devColl, page, perPage)
p.baseURL, p.targetColl, page, perPage)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -1413,6 +1424,12 @@ func DashboardHTML() string {
Telemetry Dashboard
</h1>
<div class="controls">
<select id="repoFilter" onchange="refreshData()" title="Filter by repository source">
<option value="ProxmoxVE" selected>ProxmoxVE (Production)</option>
<option value="ProxmoxVED">ProxmoxVED (Development)</option>
<option value="external">External (Forks)</option>
<option value="all">All Sources</option>
</select>
<div class="quickfilter">
<button class="filter-btn" data-days="7">7 Days</button>
<button class="filter-btn active" data-days="30">30 Days</button>
@@ -1676,8 +1693,9 @@ func DashboardHTML() string {
async function fetchData() {
const activeBtn = document.querySelector('.filter-btn.active');
const days = activeBtn ? activeBtn.dataset.days : '30';
const repo = document.getElementById('repoFilter').value;
try {
const response = await fetch('/api/dashboard?days=' + days);
const response = await fetch('/api/dashboard?days=' + days + '&repo=' + repo);
if (!response.ok) throw new Error('Failed to fetch data');
return await response.json();
} catch (error) {