Исправление: том ./mods:/lampac/mods в compose и копии 10 модулей с enable:true; конфиг без SkipModules
This commit is contained in:
@@ -0,0 +1,539 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Shared;
|
||||
using Shared.Attributes;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Telemetry.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Telemetry.Controllers;
|
||||
|
||||
[Authorization(redirectUri: "/adminpanel/auth")]
|
||||
[Route("/telemetry")]
|
||||
public class TelemetryController : BaseController
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _contextFactory;
|
||||
|
||||
public TelemetryController(IDbContextFactory<AppDbContext> contextFactory)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
private class MovieRawDto
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
public string? Tmdb { get; set; }
|
||||
public string? Kp { get; set; }
|
||||
public string? Imdb { get; set; }
|
||||
public bool IsTv { get; set; }
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
private static (DateTime start, DateTime end) GetPeriod(int days)
|
||||
{
|
||||
var now = DateTime.UtcNow.Date;
|
||||
var periodStart = days == 0 ? now : (days == -1 ? now.AddDays(-1) : now.AddDays(-days));
|
||||
var periodEnd = days == -1 ? now : DateTime.UtcNow;
|
||||
return (periodStart, periodEnd);
|
||||
}
|
||||
|
||||
private static List<object> MapMovies(List<MovieRawDto> raw)
|
||||
{
|
||||
return raw.Select(m => new
|
||||
{
|
||||
title = m.Title,
|
||||
id = m.Tmdb ?? m.Kp ?? m.Imdb ?? m.Title,
|
||||
tmdb = m.Tmdb,
|
||||
kp = m.Kp,
|
||||
imdb = m.Imdb,
|
||||
count = m.Count,
|
||||
source = m.Tmdb != null ? "tmdb" : m.Kp != null ? "kinopoisk" : m.Imdb != null ? "imdb" : "unknown",
|
||||
isTv = m.IsTv
|
||||
}).Cast<object>().ToList();
|
||||
}
|
||||
|
||||
private IQueryable<LogModelSql> ApplySearchFilter(AppDbContext db, IQueryable<LogModelSql> query, string? search)
|
||||
{
|
||||
if (string.IsNullOrEmpty(search)) return query;
|
||||
|
||||
var rawSearch = search;
|
||||
var encodedSearch = Uri.EscapeDataString(search);
|
||||
var encodedSearchPlus = encodedSearch.Replace("%20", "+");
|
||||
|
||||
return query.Where(l =>
|
||||
l.Uid.Contains(rawSearch) ||
|
||||
l.Uri.Contains(rawSearch) ||
|
||||
l.Uri.Contains(encodedSearch) ||
|
||||
l.Uri.Contains(encodedSearchPlus) ||
|
||||
(l.Balancer != null && l.Balancer.Contains(rawSearch)) ||
|
||||
db.Users.Any(u => u.Id == l.UnfoId && (u.Ip.Contains(rawSearch) || u.UserAgent.Contains(rawSearch)))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public ActionResult Index()
|
||||
{
|
||||
var path = Path.Combine(ModInit.modpath, "index.html");
|
||||
if (!System.IO.File.Exists(path)) return Content("UI missing", "text/plain");
|
||||
var html = System.IO.File.ReadAllText(path, Encoding.UTF8);
|
||||
return Content(html, "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
[HttpGet("api/stats")]
|
||||
public async Task<IActionResult> Stats(string? search, int days = 30)
|
||||
{
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var query = db.Logs.AsNoTracking();
|
||||
query = ApplySearchFilter(db, query, search);
|
||||
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
var now = DateTime.UtcNow.Date;
|
||||
|
||||
int total;
|
||||
int today;
|
||||
if (days == 0)
|
||||
{
|
||||
today = await query.Where(l => l.Time >= now.Date && l.Time < periodEnd).CountAsync();
|
||||
total = today;
|
||||
}
|
||||
else if (days == -1)
|
||||
{
|
||||
total = await query.Where(l => l.Time >= periodStart && l.Time < periodEnd).CountAsync();
|
||||
today = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
total = await query.Where(l => l.Time >= periodStart && l.Time < periodEnd).CountAsync();
|
||||
today = await query.Where(l => l.Time >= now.Date && l.Time < periodEnd).CountAsync();
|
||||
}
|
||||
|
||||
var uids = await query.Where(l => l.Time >= periodStart && l.Time < periodEnd && l.Uid != "anonymous" && !string.IsNullOrEmpty(l.Uid)).Select(l => l.Uid).Distinct().CountAsync();
|
||||
|
||||
var validLogs = query.Where(l => l.Time >= periodStart && l.Time < periodEnd && l.UnfoId != null);
|
||||
var ips = await validLogs.Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.Ip).Where(ip => ip != null && ip != "").Distinct().CountAsync();
|
||||
var uas = await validLogs.Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.UserAgent).Where(ua => ua != null && ua != "").Distinct().CountAsync();
|
||||
|
||||
return new JsonResult(new { success = true, data = new { today, total, uids, ips, uas } });
|
||||
}
|
||||
|
||||
[HttpGet("api/logs")]
|
||||
public async Task<IActionResult> Logs(string? search, int days = 30, int skip = 0, int take = 100)
|
||||
{
|
||||
take = Math.Min(take, 1000);
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
var query = db.Logs.AsNoTracking();
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
|
||||
query = query.Where(l => l.Time >= periodStart && l.Time < periodEnd);
|
||||
query = ApplySearchFilter(db, query, search);
|
||||
|
||||
var logs = await query.OrderByDescending(l => l.Time).Skip(skip).Take(take)
|
||||
.GroupJoin(db.Users, l => l.UnfoId, u => u.Id, (l, users) => new { Log = l, Users = users })
|
||||
.SelectMany(x => x.Users.DefaultIfEmpty(), (x, u) => new
|
||||
{
|
||||
id = x.Log.Id,
|
||||
time = x.Log.Time,
|
||||
uri = x.Log.Uri,
|
||||
uid = x.Log.Uid,
|
||||
ip = u != null ? u.Ip : "unknown",
|
||||
userAgent = u != null ? u.UserAgent : "unknown",
|
||||
balancer = x.Log.Balancer,
|
||||
statusCode = x.Log.StatusCode,
|
||||
duration = x.Log.DurationMs
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return new JsonResult(new { success = true, data = logs });
|
||||
}
|
||||
|
||||
[HttpGet("api/chart")]
|
||||
public async Task<IActionResult> Chart(string? search, int days = 30)
|
||||
{
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var query = db.Logs.AsNoTracking();
|
||||
query = ApplySearchFilter(db, query, search);
|
||||
|
||||
var data = await query
|
||||
.Where(l => l.Time >= periodStart && l.Time < periodEnd)
|
||||
.GroupBy(l => l.Time.Date)
|
||||
.Select(g => new { date = g.Key, count = g.Count() })
|
||||
.OrderBy(x => x.date)
|
||||
.ToListAsync();
|
||||
|
||||
return new JsonResult(new { success = true, data = data });
|
||||
}
|
||||
|
||||
[HttpGet("api/tops")]
|
||||
public async Task<IActionResult> GetTops(string? search, [FromQuery] int days = 7)
|
||||
{
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
|
||||
var query = db.Logs.AsNoTracking();
|
||||
query = ApplySearchFilter(db, query, search);
|
||||
var baseQuery = query.Where(l => l.Time >= periodStart && l.Time < periodEnd);
|
||||
|
||||
var topBalancers = await baseQuery
|
||||
.Where(l => l.Balancer != null && l.Balancer != "" && !l.Balancer.StartsWith("18+ "))
|
||||
.GroupBy(l => l.Balancer)
|
||||
.Select(g => new { balancer = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var topAdult = await baseQuery
|
||||
.Where(l => l.Balancer != null && l.Balancer.StartsWith("18+ "))
|
||||
.GroupBy(l => l.Balancer)
|
||||
.Select(g => new { balancer = g.Key!.Substring(4), count = g.Count() })
|
||||
.OrderByDescending(x => x.count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var topUsers = await baseQuery
|
||||
.Where(l => l.Uid != "anonymous" && l.Uid != null && l.Uid != "")
|
||||
.GroupBy(l => l.Uid)
|
||||
.Select(g => new { uid = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var topMoviesRaw = await baseQuery
|
||||
.Where(l => l.MovieTitle != null && l.MovieTitle != "")
|
||||
.GroupBy(l => l.MovieTitle)
|
||||
.Select(g => new MovieRawDto
|
||||
{
|
||||
Title = g.Key,
|
||||
Tmdb = g.Max(l => l.TmdbId),
|
||||
Kp = g.Max(l => l.KpId),
|
||||
Imdb = g.Max(l => l.ImdbId),
|
||||
IsTv = g.Max(l => l.IsTv ? 1 : 0) == 1,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var topMovies = MapMovies(topMoviesRaw);
|
||||
|
||||
var userAgents = await baseQuery
|
||||
.Where(l => l.UnfoId != null)
|
||||
.Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.UserAgent)
|
||||
.GroupBy(ua => ua)
|
||||
.Select(g => new { ua = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count)
|
||||
.Take(1000)
|
||||
.ToListAsync();
|
||||
|
||||
var devices = userAgents
|
||||
.Select(x => new { name = ParseDevice(x.ua), count = x.count })
|
||||
.GroupBy(x => x.name)
|
||||
.Select(g => new { name = g.Key, count = g.Sum(x => x.count) })
|
||||
.OrderByDescending(x => x.count)
|
||||
.ToList();
|
||||
|
||||
var hours = await baseQuery
|
||||
.GroupBy(l => l.Time.Hour)
|
||||
.Select(g => new { hour = g.Key, count = g.Count() })
|
||||
.OrderBy(x => x.hour)
|
||||
.ToListAsync();
|
||||
|
||||
var seriesCount = await baseQuery.Where(l => l.IsTv).CountAsync();
|
||||
var moviesCount = await baseQuery.Where(l => l.MovieTitle != null && l.MovieTitle != "" && !l.IsTv).CountAsync();
|
||||
var animeCount = await baseQuery.Where(l => l.Uri.Contains("anime=1") || l.Uri.Contains("anime=true")).CountAsync();
|
||||
|
||||
var contentTypes = new[] {
|
||||
new { name = "Фильмы", count = moviesCount },
|
||||
new { name = "Сериалы", count = seriesCount },
|
||||
new { name = "Аниме", count = animeCount }
|
||||
}.Where(x => x.count > 0).ToList();
|
||||
|
||||
return new JsonResult(new { success = true, data = new { balancers = topBalancers, adult = topAdult, users = topUsers, movies = topMovies, devices, hours, contentTypes } });
|
||||
}
|
||||
|
||||
private string ParseDevice(string? ua)
|
||||
{
|
||||
if (ua == null) return "Другие";
|
||||
ua = ua.ToLower();
|
||||
if (ua.Contains("tizen") || ua.Contains("samsung")) return "Tizen";
|
||||
if (ua.Contains("web0s") || ua.Contains("webos")) return "WebOS";
|
||||
if (ua.Contains("android")) return "Android";
|
||||
if (ua.Contains("iphone") || ua.Contains("ipad") || ua.Contains("mac os")) return "Apple";
|
||||
if (ua.Contains("windows")) return "Windows";
|
||||
if (ua.Contains("linux")) return "Linux";
|
||||
return "Другие";
|
||||
}
|
||||
|
||||
[HttpGet("api/user")]
|
||||
public async Task<IActionResult> UserInfo(string uid, int days = 30)
|
||||
{
|
||||
if (string.IsNullOrEmpty(uid)) return BadRequest();
|
||||
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
|
||||
var baseQuery = db.Logs.AsNoTracking().Where(l => l.Uid == uid && l.Time >= periodStart && l.Time < periodEnd);
|
||||
var count = await baseQuery.CountAsync();
|
||||
|
||||
if (count == 0) return new JsonResult(new { success = false });
|
||||
|
||||
var totalRequests = count;
|
||||
var firstSeen = await baseQuery.MinAsync(l => l.Time);
|
||||
var lastSeen = await baseQuery.MaxAsync(l => l.Time);
|
||||
|
||||
var ips = await baseQuery.Where(l => l.UnfoId != null).Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.Ip).Where(ip => ip != null && ip != "").Distinct().ToListAsync();
|
||||
var userAgents = await baseQuery.Where(l => l.UnfoId != null).Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.UserAgent).Where(ua => ua != null && ua != "").Distinct().ToListAsync();
|
||||
|
||||
var balancers = await baseQuery.Where(l => l.Balancer != null && l.Balancer != "")
|
||||
.GroupBy(l => l.Balancer)
|
||||
.Select(g => new { name = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count)
|
||||
.ToListAsync();
|
||||
|
||||
var isHourly = days <= 1;
|
||||
var timeline = await (isHourly
|
||||
? baseQuery.GroupBy(l => new DateTime(l.Time.Year, l.Time.Month, l.Time.Day, l.Time.Hour, 0, 0)).Select(g => new { date = g.Key, count = g.Count() }).OrderBy(x => x.date).ToListAsync()
|
||||
: baseQuery.GroupBy(l => l.Time.Date).Select(g => new { date = g.Key, count = g.Count() }).OrderBy(x => x.date).ToListAsync());
|
||||
|
||||
var moviesRaw = await baseQuery
|
||||
.Where(l => l.MovieTitle != null && l.MovieTitle != "")
|
||||
.GroupBy(l => l.MovieTitle)
|
||||
.Select(g => new MovieRawDto
|
||||
{
|
||||
Title = g.Key,
|
||||
Tmdb = g.Max(l => l.TmdbId),
|
||||
Kp = g.Max(l => l.KpId),
|
||||
Imdb = g.Max(l => l.ImdbId),
|
||||
IsTv = g.Max(l => l.IsTv ? 1 : 0) == 1,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var movies = MapMovies(moviesRaw);
|
||||
|
||||
return new JsonResult(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
totalRequests,
|
||||
firstSeen,
|
||||
lastSeen,
|
||||
ips,
|
||||
userAgents,
|
||||
balancers,
|
||||
timeline,
|
||||
movies
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("api/ip")]
|
||||
public async Task<IActionResult> IpInfo(string ip, int days = 30)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ip)) return BadRequest();
|
||||
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
|
||||
var baseQuery = db.Logs.AsNoTracking().Where(l => l.Time >= periodStart && l.Time < periodEnd && l.UnfoId != null && db.Users.Any(u => u.Id == l.UnfoId && u.Ip == ip));
|
||||
var count = await baseQuery.CountAsync();
|
||||
|
||||
if (count == 0) return new JsonResult(new { success = false });
|
||||
|
||||
var totalRequests = count;
|
||||
var firstSeen = await baseQuery.MinAsync(l => l.Time);
|
||||
var lastSeen = await baseQuery.MaxAsync(l => l.Time);
|
||||
|
||||
var uids = await baseQuery.Where(l => l.Uid != "anonymous" && l.Uid != null && l.Uid != "").Select(l => l.Uid).Distinct().ToListAsync();
|
||||
var userAgents = await baseQuery.Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.UserAgent).Where(ua => ua != null && ua != "").Distinct().ToListAsync();
|
||||
|
||||
var balancers = await baseQuery.Where(l => l.Balancer != null && l.Balancer != "")
|
||||
.GroupBy(l => l.Balancer)
|
||||
.Select(g => new { name = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count)
|
||||
.ToListAsync();
|
||||
|
||||
var isHourly = days <= 1;
|
||||
var timeline = await (isHourly
|
||||
? baseQuery.GroupBy(l => new DateTime(l.Time.Year, l.Time.Month, l.Time.Day, l.Time.Hour, 0, 0)).Select(g => new { date = g.Key, count = g.Count() }).OrderBy(x => x.date).ToListAsync()
|
||||
: baseQuery.GroupBy(l => l.Time.Date).Select(g => new { date = g.Key, count = g.Count() }).OrderBy(x => x.date).ToListAsync());
|
||||
|
||||
var moviesRaw = await baseQuery
|
||||
.Where(l => l.MovieTitle != null && l.MovieTitle != "")
|
||||
.GroupBy(l => l.MovieTitle)
|
||||
.Select(g => new MovieRawDto
|
||||
{
|
||||
Title = g.Key,
|
||||
Tmdb = g.Max(l => l.TmdbId),
|
||||
Kp = g.Max(l => l.KpId),
|
||||
Imdb = g.Max(l => l.ImdbId),
|
||||
IsTv = g.Max(l => l.IsTv ? 1 : 0) == 1,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var movies = MapMovies(moviesRaw);
|
||||
|
||||
return new JsonResult(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
totalRequests,
|
||||
firstSeen,
|
||||
lastSeen,
|
||||
uids,
|
||||
userAgents,
|
||||
balancers,
|
||||
timeline,
|
||||
movies
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("api/settings")]
|
||||
public IActionResult GetSettings()
|
||||
{
|
||||
return new JsonResult(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
prefixes = string.Join("\n", RequestListener.SkipPrefixes),
|
||||
extensions = string.Join("\n", RequestListener.SkipExtensions),
|
||||
regexes = string.Join("\n", RequestListener.RegexFilters),
|
||||
ips = string.Join("\n", RequestListener.BlacklistIps),
|
||||
rateLimit = RequestListener.RateLimitMs
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("api/settings")]
|
||||
public IActionResult UpdateSettings([FromForm] string? prefixes, [FromForm] string? extensions, [FromForm] string? regexes, [FromForm] string? ips, [FromForm] int? rateLimit)
|
||||
{
|
||||
if (prefixes != null) RequestListener.SkipPrefixes = prefixes.Split(new[] { '\n', ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
|
||||
if (extensions != null) RequestListener.SkipExtensions = extensions.Split(new[] { '\n', ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
|
||||
if (regexes != null)
|
||||
{
|
||||
var patterns = regexes.Split(new[] { '\n', ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
|
||||
var invalid = new List<string>();
|
||||
foreach (var p in patterns)
|
||||
{
|
||||
try { System.Text.RegularExpressions.Regex.Match("", p, System.Text.RegularExpressions.RegexOptions.None, TimeSpan.FromMilliseconds(50)); }
|
||||
catch { invalid.Add(p); }
|
||||
}
|
||||
if (invalid.Count > 0)
|
||||
return new JsonResult(new { success = false, error = $"Невалидные regex: {string.Join(", ", invalid)}" });
|
||||
RequestListener.RegexFilters = patterns;
|
||||
}
|
||||
if (ips != null) RequestListener.BlacklistIps = ips.Split(new[] { '\n', ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
|
||||
if (rateLimit.HasValue) RequestListener.RateLimitMs = rateLimit.Value;
|
||||
|
||||
RequestListener.SaveSettings();
|
||||
|
||||
return new JsonResult(new { success = true });
|
||||
}
|
||||
|
||||
[HttpGet("api/balancer")]
|
||||
public async Task<IActionResult> BalancerInfo(string balancer, int days = 30)
|
||||
{
|
||||
if (string.IsNullOrEmpty(balancer)) return BadRequest();
|
||||
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var (periodStart, periodEnd) = GetPeriod(days);
|
||||
var now = DateTime.UtcNow.Date;
|
||||
|
||||
var baseQuery = db.Logs.AsNoTracking().Where(l => l.Balancer == balancer && l.Time >= periodStart && l.Time < periodEnd);
|
||||
|
||||
var total = await baseQuery.CountAsync();
|
||||
var today = await baseQuery.Where(l => l.Time >= now.Date).CountAsync();
|
||||
|
||||
var topUids = await baseQuery.Where(l => l.Uid != "anonymous" && l.Uid != null && l.Uid != "")
|
||||
.GroupBy(l => l.Uid)
|
||||
.Select(g => new { uid = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count).Take(10).ToListAsync();
|
||||
|
||||
var topIps = await baseQuery.Where(l => l.UnfoId != null)
|
||||
.Join(db.Users, l => l.UnfoId, u => u.Id, (l, u) => u.Ip)
|
||||
.Where(ip => ip != null && ip != "")
|
||||
.GroupBy(ip => ip)
|
||||
.Select(g => new { ip = g.Key, count = g.Count() })
|
||||
.OrderByDescending(x => x.count).Take(10).ToListAsync();
|
||||
|
||||
var isHourly = days <= 1;
|
||||
var timeline = await (isHourly
|
||||
? baseQuery.GroupBy(l => new DateTime(l.Time.Year, l.Time.Month, l.Time.Day, l.Time.Hour, 0, 0)).Select(g => new { date = g.Key, count = g.Count() }).OrderBy(x => x.date).ToListAsync()
|
||||
: baseQuery.GroupBy(l => l.Time.Date).Select(g => new { date = g.Key, count = g.Count() }).OrderBy(x => x.date).ToListAsync());
|
||||
|
||||
var moviesRaw = await baseQuery
|
||||
.Where(l => l.MovieTitle != null && l.MovieTitle != "")
|
||||
.GroupBy(l => l.MovieTitle)
|
||||
.Select(g => new MovieRawDto
|
||||
{
|
||||
Title = g.Key,
|
||||
Tmdb = g.Max(l => l.TmdbId),
|
||||
Kp = g.Max(l => l.KpId),
|
||||
Imdb = g.Max(l => l.ImdbId),
|
||||
IsTv = g.Max(l => l.IsTv ? 1 : 0) == 1,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var movies = MapMovies(moviesRaw);
|
||||
|
||||
return new JsonResult(new { success = true, data = new { total, today, uids = topUids, ips = topIps, timeline, movies } });
|
||||
}
|
||||
|
||||
[HttpPost("api/logs/delete")]
|
||||
public async Task<IActionResult> DeleteLogs([FromForm] string? search, [FromForm] int days = 30)
|
||||
{
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
var now = DateTime.UtcNow.Date;
|
||||
var periodStart = days == 0 ? now : (days == -1 ? now.AddDays(-1) : now.AddDays(-days));
|
||||
var periodEnd = days == -1 ? now : DateTime.UtcNow;
|
||||
|
||||
var query = db.Logs.Where(l => l.Time >= periodStart && l.Time < periodEnd);
|
||||
query = ApplySearchFilter(db, query, search);
|
||||
|
||||
var deleted = await query.ExecuteDeleteAsync();
|
||||
return new JsonResult(new { success = true, deleted });
|
||||
}
|
||||
|
||||
[HttpGet("api/export")]
|
||||
public async Task<IActionResult> ExportData()
|
||||
{
|
||||
await using var db = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
var result = await db.Logs.AsNoTracking()
|
||||
.OrderByDescending(l => l.Id)
|
||||
.Take(50000)
|
||||
.GroupJoin(db.Users, l => l.UnfoId, u => u.Id, (l, users) => new { Log = l, Users = users })
|
||||
.SelectMany(x => x.Users.DefaultIfEmpty(), (x, u) => new
|
||||
{
|
||||
time = x.Log.Time,
|
||||
uri = x.Log.Uri,
|
||||
uid = x.Log.Uid,
|
||||
ip = u != null ? u.Ip : "",
|
||||
userAgent = u != null ? u.UserAgent : "",
|
||||
balancer = x.Log.Balancer,
|
||||
statusCode = x.Log.StatusCode
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return new JsonResult(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user