Initial commit
This commit is contained in:
commit
77d35ff965
51 changed files with 5591 additions and 0 deletions
232
Services/ForgejoService.cs
Normal file
232
Services/ForgejoService.cs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
using Dapper;
|
||||
using Npgsql;
|
||||
using PlanTempusAdmin.Models;
|
||||
|
||||
namespace PlanTempusAdmin.Services;
|
||||
|
||||
public class ForgejoService
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly ILogger<ForgejoService> _logger;
|
||||
|
||||
static ForgejoService()
|
||||
{
|
||||
DefaultTypeMap.MatchNamesWithUnderscores = true;
|
||||
}
|
||||
|
||||
public ForgejoService(IConfiguration configuration, ILogger<ForgejoService> logger)
|
||||
{
|
||||
_connectionString = configuration.GetConnectionString("ForgejoDb")
|
||||
?? throw new InvalidOperationException("ForgejoDb connection string not configured");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> TestConnectionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not connect to Forgejo database");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ForgejoDashboard> GetDashboardAsync()
|
||||
{
|
||||
var dashboard = new ForgejoDashboard();
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
|
||||
// Repository stats
|
||||
var repoStats = await connection.QuerySingleOrDefaultAsync<dynamic>(@"
|
||||
SELECT
|
||||
COUNT(*)::int as total,
|
||||
COUNT(*) FILTER (WHERE NOT is_private)::int as public_repos,
|
||||
COUNT(*) FILTER (WHERE is_private)::int as private_repos,
|
||||
COUNT(*) FILTER (WHERE is_fork)::int as forked,
|
||||
COUNT(*) FILTER (WHERE is_archived)::int as archived,
|
||||
COUNT(*) FILTER (WHERE is_mirror)::int as mirrors,
|
||||
COALESCE(SUM(size), 0) as total_size,
|
||||
COALESCE(SUM(num_stars), 0)::int as total_stars,
|
||||
COALESCE(SUM(num_forks), 0)::int as total_forks,
|
||||
COALESCE(SUM(num_issues - num_closed_issues), 0)::int as open_issues,
|
||||
COALESCE(SUM(num_pulls - num_closed_pulls), 0)::int as open_prs
|
||||
FROM repository");
|
||||
|
||||
if (repoStats != null)
|
||||
{
|
||||
dashboard.TotalRepos = (int)repoStats.total;
|
||||
dashboard.PublicRepos = (int)repoStats.public_repos;
|
||||
dashboard.PrivateRepos = (int)repoStats.private_repos;
|
||||
dashboard.ForkedRepos = (int)repoStats.forked;
|
||||
dashboard.ArchivedRepos = (int)repoStats.archived;
|
||||
dashboard.MirrorRepos = (int)repoStats.mirrors;
|
||||
dashboard.TotalSize = (long)repoStats.total_size;
|
||||
dashboard.TotalStars = (int)repoStats.total_stars;
|
||||
dashboard.TotalForks = (int)repoStats.total_forks;
|
||||
dashboard.TotalOpenIssues = (int)repoStats.open_issues;
|
||||
dashboard.TotalOpenPRs = (int)repoStats.open_prs;
|
||||
}
|
||||
|
||||
// Actions stats
|
||||
var actionStats = await connection.QuerySingleOrDefaultAsync<dynamic>(@"
|
||||
SELECT
|
||||
COUNT(*)::int as total,
|
||||
COUNT(*) FILTER (WHERE TO_TIMESTAMP(created) >= NOW() - INTERVAL '1 day')::int as today,
|
||||
COUNT(*) FILTER (WHERE TO_TIMESTAMP(created) >= NOW() - INTERVAL '7 days')::int as this_week,
|
||||
COUNT(*) FILTER (WHERE status = 3)::int as successful,
|
||||
COUNT(*) FILTER (WHERE status = 4)::int as failed,
|
||||
COUNT(*) FILTER (WHERE status = 2)::int as running
|
||||
FROM action_run");
|
||||
|
||||
if (actionStats != null)
|
||||
{
|
||||
dashboard.TotalRuns = (int)actionStats.total;
|
||||
dashboard.RunsToday = (int)actionStats.today;
|
||||
dashboard.RunsThisWeek = (int)actionStats.this_week;
|
||||
dashboard.SuccessfulRuns = (int)actionStats.successful;
|
||||
dashboard.FailedRunsCount = (int)actionStats.failed;
|
||||
dashboard.RunningNow = (int)actionStats.running;
|
||||
}
|
||||
|
||||
// Recently updated repos
|
||||
dashboard.RecentlyUpdated = await GetRepositoriesAsync(connection, "ORDER BY r.updated_unix DESC LIMIT 5");
|
||||
|
||||
// Largest repos
|
||||
dashboard.LargestRepos = await GetRepositoriesAsync(connection, "ORDER BY r.size DESC LIMIT 5");
|
||||
|
||||
// Recent action runs
|
||||
dashboard.RecentRuns = await GetActionRunsAsync(connection, "ORDER BY ar.created DESC LIMIT 10");
|
||||
|
||||
// Failed runs
|
||||
dashboard.FailedRuns = await GetActionRunsAsync(connection, "WHERE ar.status = 4 ORDER BY ar.created DESC LIMIT 5");
|
||||
|
||||
// Running now
|
||||
dashboard.RunningRuns = await GetActionRunsAsync(connection, "WHERE ar.status = 2 ORDER BY ar.started LIMIT 10");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching Forgejo dashboard");
|
||||
}
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
public async Task<List<ForgejoRepository>> GetAllRepositoriesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
return await GetRepositoriesAsync(connection, "ORDER BY LOWER(u.name), LOWER(r.name)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching repositories");
|
||||
return new List<ForgejoRepository>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<ForgejoActionRun>> GetAllActionRunsAsync(int limit = 100)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
return await GetActionRunsAsync(connection, $"ORDER BY ar.created DESC LIMIT {limit}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching action runs");
|
||||
return new List<ForgejoActionRun>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<ForgejoActionStats>> GetActionStatsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(_connectionString);
|
||||
var stats = await connection.QueryAsync<ForgejoActionStats>(@"
|
||||
SELECT
|
||||
ar.workflow_id,
|
||||
r.name as repo_name,
|
||||
COUNT(*)::int as total_runs,
|
||||
COUNT(*) FILTER (WHERE ar.status = 3)::int as successful,
|
||||
COUNT(*) FILTER (WHERE ar.status = 4)::int as failed,
|
||||
TO_TIMESTAMP(MAX(ar.created)) as last_run,
|
||||
AVG(ar.stopped - ar.started) FILTER (WHERE ar.stopped > 0 AND ar.started > 0) as avg_duration_seconds
|
||||
FROM action_run ar
|
||||
JOIN repository r ON ar.repo_id = r.id
|
||||
GROUP BY ar.workflow_id, r.name
|
||||
ORDER BY total_runs DESC");
|
||||
return stats.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching action stats");
|
||||
return new List<ForgejoActionStats>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<ForgejoRepository>> GetRepositoriesAsync(NpgsqlConnection connection, string orderClause)
|
||||
{
|
||||
var repos = await connection.QueryAsync<ForgejoRepository>($@"
|
||||
SELECT
|
||||
r.id,
|
||||
u.name as owner_name,
|
||||
r.name,
|
||||
r.description,
|
||||
r.is_private,
|
||||
r.is_fork,
|
||||
r.is_archived,
|
||||
r.is_mirror,
|
||||
r.num_stars,
|
||||
r.num_forks,
|
||||
r.num_watches,
|
||||
r.num_issues,
|
||||
r.num_closed_issues,
|
||||
r.num_pulls,
|
||||
r.num_closed_pulls,
|
||||
r.size,
|
||||
TO_TIMESTAMP(r.created_unix) as created_at,
|
||||
TO_TIMESTAMP(r.updated_unix) as updated_at
|
||||
FROM repository r
|
||||
JOIN ""user"" u ON r.owner_id = u.id
|
||||
{orderClause}");
|
||||
return repos.ToList();
|
||||
}
|
||||
|
||||
private async Task<List<ForgejoActionRun>> GetActionRunsAsync(NpgsqlConnection connection, string whereOrderClause)
|
||||
{
|
||||
var runs = await connection.QueryAsync<ForgejoActionRun>($@"
|
||||
SELECT
|
||||
ar.id,
|
||||
ar.repo_id,
|
||||
r.name as repo_name,
|
||||
u.name as owner_name,
|
||||
ar.workflow_id,
|
||||
ar.""index"",
|
||||
COALESCE(tu.name, '') as trigger_user,
|
||||
ar.ref,
|
||||
ar.commit_sha,
|
||||
ar.event,
|
||||
ar.title,
|
||||
ar.status,
|
||||
CASE WHEN ar.started > 0 THEN TO_TIMESTAMP(ar.started) ELSE NULL END as started,
|
||||
CASE WHEN ar.stopped > 0 THEN TO_TIMESTAMP(ar.stopped) ELSE NULL END as stopped,
|
||||
TO_TIMESTAMP(ar.created) as created,
|
||||
TO_TIMESTAMP(ar.updated) as updated
|
||||
FROM action_run ar
|
||||
JOIN repository r ON ar.repo_id = r.id
|
||||
JOIN ""user"" u ON r.owner_id = u.id
|
||||
LEFT JOIN ""user"" tu ON ar.trigger_user_id = tu.id
|
||||
{whereOrderClause}");
|
||||
return runs.ToList();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue