using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Nettle; using Wabbajack.Common; using Wabbajack.Server; using Wabbajack.Server.DataLayer; using Wabbajack.Server.DTOs; using Wabbajack.Server.Services; using WebSocketSharp; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Wabbajack.BuildServer.Controllers { [ApiController] [Route("/metrics")] public class MetricsController : ControllerBase { private SqlService _sql; private ILogger _logger; private MetricsKeyCache _keyCache; public MetricsController(ILogger logger, SqlService sql, MetricsKeyCache keyCache) { _sql = sql; _logger = logger; _keyCache = keyCache; } [HttpGet] [Route("{subject}/{value}")] public async Task LogMetricAsync(string subject, string value) { var date = DateTime.UtcNow; var metricsKey = Request.Headers[Consts.MetricsKeyHeader].FirstOrDefault(); if (metricsKey != null) await _keyCache.AddKey(metricsKey); // Used in tests if (value == "Default" || value == "untitled" || subject == "failed_download" || Guid.TryParse(value, out _)) return new Result { Timestamp = date}; await Log(date, subject, value, metricsKey); return new Result { Timestamp = date}; } [HttpGet] [Route("report/{subject}")] [ResponseCache(Duration = 60 * 60)] public async Task MetricsReport(string subject) { var metrics = (await _sql.MetricsReport(subject)).ToList(); var labels = metrics.GroupBy(m => m.Date) .OrderBy(m => m.Key) .Select(m => m.Key) .ToArray(); var labelStrings = labels.Select(l => l.ToString("MM-dd-yyy")).ToList(); var results = metrics .GroupBy(m => m.Subject) .Select(g => { var indexed = g.ToDictionary(m => m.Date, m => m.Count); return new MetricResult { SeriesName = g.Key, Labels = labelStrings, Values = labels.Select(l => indexed.TryGetValue(l, out var found) ? found : 0).ToList() }; }); return Ok(results.ToList()); } [HttpGet] [Route("badge/{name}/total_installs_badge.json")] public async Task TotalInstallsBadge(string name) { var results = await _sql.TotalInstalls(name); Response.ContentType = "application/json"; return Ok(results == 0 ? new Badge($"Modlist {name} not found!", "Error") {color = "red"} : new Badge("Installations: ", "____") {color = "green"}); } [HttpGet] [Route("badge/{name}/unique_installs_badge.json")] public async Task UniqueInstallsBadge(string name) { var results = await _sql.UniqueInstalls(name); Response.ContentType = "application/json"; return Ok(results == 0 ? new Badge($"Modlist {name} not found!", "Error") {color = "red"} : new Badge("Installations: ", "____"){color = "green"}) ; } private static readonly Func ReportTemplate = NettleEngine.GetCompiler().Compile(@"

Tar Report for {{$.key}}

Ban Status: {{$.status}}

{{each $.log }} {{/each}}
{{$.Timestamp}} {{$.Path}} {{$.Key}}
"); [HttpGet] [Route("tarlog/{key}")] public async Task TarLog(string key) { var isTarKey = await _sql.IsTarKey(key); List<(DateTime, string, string)> report = new List<(DateTime, string, string)>(); if (isTarKey) report = await _sql.FullTarReport(key); var response = ReportTemplate(new { key = key, status = isTarKey ? "BANNED" : "NOT BANNED", log = report.Select(entry => new { Timestamp = entry.Item1, Path = entry.Item2, Key = entry.Item3 }).ToList() }); return new ContentResult { ContentType = "text/html", StatusCode = (int) HttpStatusCode.OK, Content = response }; } private async Task Log(DateTime timestamp, string action, string subject, string metricsKey = null) { //_logger.Log(LogLevel.Information, $"Log - {timestamp} {action} {subject} {metricsKey}"); await _sql.IngestMetric(new Metric { Timestamp = timestamp, Action = action, Subject = subject, MetricsKey = metricsKey }); } public class Result { public DateTime Timestamp { get; set; } } class TotalListTemplateData { public string Title { get; set; } public long Total { get; set; } public Item[] Items { get; set; } public class Item { public long Count { get; set; } public string Title { get; set; } } } private static Func _totalListTemplate; private static Func TotalListTemplate { get { if (_totalListTemplate == null) { var resource = Assembly.GetExecutingAssembly() .GetManifestResourceStream("Wabbajack.Server.Controllers.Templates.TotalListTemplate.html")! .ReadAll(); _totalListTemplate = NettleEngine.GetCompiler().Compile(Encoding.UTF8.GetString(resource)); } return _totalListTemplate; } } [HttpGet("total_installs.html")] [ResponseCache(Duration = 60 * 60)] public async Task TotalInstalls() { var data = await _sql.GetTotalInstalls(); var result = TotalListTemplate(new TotalListTemplateData { Title = "Total Installs", Total = data.Sum(d => d.Item2), Items = data.Select(d => new TotalListTemplateData.Item {Title = d.Item1, Count = d.Item2}) .ToArray() }); return new ContentResult { ContentType = "text/html", StatusCode = (int)HttpStatusCode.OK, Content = result}; } [HttpGet("total_unique_installs.html")] [ResponseCache(Duration = 60 * 60)] public async Task TotalUniqueInstalls() { var data = await _sql.GetTotalUniqueInstalls(); var result = TotalListTemplate(new TotalListTemplateData { Title = "Total Unique Installs", Total = data.Sum(d => d.Item2), Items = data.Select(d => new TotalListTemplateData.Item {Title = d.Item1, Count = d.Item2}) .ToArray() }); return new ContentResult { ContentType = "text/html", StatusCode = (int)HttpStatusCode.OK, Content = result}; } [HttpGet("dump.json")] public async Task DataDump() { return Ok(await _sql.MetricsDump().ToArrayAsync()); } } }