using System.IO; using System.Reflection; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.RazorPages; using QuantEngine.Application.Interfaces; using QuantEngine.Core.Interfaces; using QuantEngine.Web.Services; namespace QuantEngine.Web.Pages.Admin.Dashboard; [Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)] public class IndexModel : PageModel { private readonly IWorkspaceRepository _workspaceRepository; private readonly ICollectionReadModelService _collectionReadModelService; private readonly IWebHostEnvironment _environment; private readonly ILogger _logger; public int? ActiveUsersCount { get; set; } public int? RecentRunsCount { get; set; } public bool IsDatabaseConnected { get; set; } public string EnvironmentName => _environment.EnvironmentName; public string AppVersion { get { // 1. Try reading version.txt from application base directory try { var txtPath = Path.Combine(AppContext.BaseDirectory, "version.txt"); if (System.IO.File.Exists(txtPath)) { var txtVersion = System.IO.File.ReadAllText(txtPath).Trim(); if (!string.IsNullOrEmpty(txtVersion)) return txtVersion; } } catch {} // 2. Try reading raw assembly version var rawVersion = Assembly.GetEntryAssembly() ?.GetCustomAttribute() ?.InformationalVersion; if (!string.IsNullOrEmpty(rawVersion) && rawVersion.StartsWith("quant_", StringComparison.OrdinalIgnoreCase)) { return rawVersion; } // 3. Fallback to dynamic local Git parsing if available var dateStr = DateTime.UtcNow.AddHours(9).ToString("yyyyMMdd"); string gitHash = "024122d"; try { var baseDir = AppContext.BaseDirectory; var current = new DirectoryInfo(baseDir); string? repoRoot = null; while (current != null) { if (Directory.Exists(Path.Combine(current.FullName, ".git"))) { repoRoot = current.FullName; break; } current = current.Parent; } if (repoRoot != null) { var headPath = Path.Combine(repoRoot, ".git", "HEAD"); if (System.IO.File.Exists(headPath)) { var headContent = System.IO.File.ReadAllText(headPath).Trim(); if (headContent.StartsWith("ref:")) { var refPath = Path.Combine(repoRoot, ".git", headContent.Substring(4).Trim().Replace('/', Path.DirectorySeparatorChar)); if (System.IO.File.Exists(refPath)) { var fullHash = System.IO.File.ReadAllText(refPath).Trim(); if (fullHash.Length >= 7) gitHash = fullHash.Substring(0, 7); } } else if (headContent.Length >= 7) { gitHash = headContent.Substring(0, 7); } } } } catch {} int runCount = 11; return $"quant_{dateStr}.{runCount}.{gitHash}"; } } public IndexModel( IWorkspaceRepository workspaceRepository, ICollectionReadModelService collectionReadModelService, IWebHostEnvironment environment, ILogger logger) { _workspaceRepository = workspaceRepository; _collectionReadModelService = collectionReadModelService; _environment = environment; _logger = logger; } public async Task OnGetAsync() { try { var accounts = await _workspaceRepository.GetAccountsAsync(); ActiveUsersCount = accounts.Count(a => string.Equals(a.IsActive, "true", StringComparison.OrdinalIgnoreCase)); var dashboard = await _collectionReadModelService.GetDashboardStateAsync(); RecentRunsCount = string.IsNullOrEmpty(dashboard?.LastRunId) ? 0 : 1; // These two queries only complete if the DB round-trip actually // succeeded, so reaching this line is the real signal -- do not // hardcode a static "정상"/"연결됨" badge independent of it. IsDatabaseConnected = true; } catch (Exception ex) { _logger.LogError(ex, "Dashboard data loading failed"); IsDatabaseConnected = false; } } }