InventoryAgent/Inventory.Api/Program.cs
2025-10-20 00:03:49 +08:00

258 lines
9.4 KiB
C#

using System.Text.Json;
using Inventory.Api.Models;
using Inventory.Api.Services;
using Inventory.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using QuestPDF.Infrastructure;
QuestPDF.Settings.License = LicenseType.Community;
var builder = WebApplication.CreateBuilder(args);
// Setup secrets from .env or Vault
EnvironmentBuilder.SetupEnvironment(builder.Environment);
var dbCon = Secrets.DbConnectionString;
if (string.IsNullOrWhiteSpace(dbCon))
{
throw new InvalidOperationException("FATAL ERROR: DB_CONNECTION_STRING is not configured.");
}
builder.Services.AddDbContext<InventoryContext>(options => options.UseSqlServer(dbCon!));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFlutterApp",
policy =>
{
// In production, you should lock this down to your Flutter app's domain.
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Add a redirect from the root URL to the Swagger UI for convenience in development.
app.MapGet("/", (HttpContext context) =>
{
context.Response.Redirect("/swagger");
return Task.CompletedTask;
}).ExcludeFromDescription(); // Exclude this from the OpenAPI spec
// Only use HTTPS redirection in non-development environments.
// This prevents the "Failed to determine the https port for redirect" warning during local development.
if (!app.Environment.IsDevelopment()) {
app.UseHttpsRedirection();
}
app.UseCors("AllowFlutterApp");
var api = app.MapGroup("/api");
var dashboardApi = api.MapGroup("/dashboard");
var devicesApi = api.MapGroup("/devices");
var exportApi = api.MapGroup("/export");
// --- Dashboard Endpoints ---
dashboardApi.MapGet("/summary", async (InventoryContext db) =>
{
var devices = await db.Devices.AsNoTracking().ToListAsync();
var printersWithSerial = 0;
var printersWithoutSerial = 0;
foreach (var device in devices.Where(d => !string.IsNullOrEmpty(d.Printers)))
{
try
{
var printers = JsonSerializer.Deserialize<List<PrinterInfo>>(device.Printers!);
printersWithSerial += printers?.Count(p => !string.IsNullOrWhiteSpace(p.SerialNumber)) ?? 0;
printersWithoutSerial += printers?.Count(p => string.IsNullOrWhiteSpace(p.SerialNumber)) ?? 0;
}
catch { /* Ignore deserialization errors */ }
}
var drivesFailing = 0;
foreach (var device in devices.Where(d => !string.IsNullOrEmpty(d.DriveHealth)))
{
try
{
var drives = JsonSerializer.Deserialize<List<DriveHealthInfo>>(device.DriveHealth!);
if (drives?.Any(d => d.IsFailing == true) == true)
{
drivesFailing++;
}
}
catch { /* Ignore deserialization errors */ }
}
return Results.Ok(new
{
TotalDevices = devices.Count,
TotalLaptops = devices.Count(d => d.DeviceType == "Laptop"),
TotalDesktops = devices.Count(d => d.DeviceType == "Desktop"),
TotalServers = devices.Count(d => d.DeviceType == "Server"),
PrintersWithSerial = printersWithSerial,
PrintersWithoutSerial = printersWithoutSerial,
DrivesFailing = drivesFailing
});
});
dashboardApi.MapGet("/os-distribution", async (InventoryContext db) =>
{
var osDistribution = await db.Devices
.AsNoTracking()
.GroupBy(d => d.OSVersion)
.Select(g => new { OSVersion = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count)
.ToListAsync();
return Results.Ok(osDistribution);
});
dashboardApi.MapGet("/storage-by-type", async (InventoryContext db) =>
{
var devicesWithStorage = await db.Devices
.AsNoTracking()
.Where(d => !string.IsNullOrEmpty(d.StorageDevices))
.Select(d => d.StorageDevices)
.ToListAsync();
ulong totalSsd = 0;
ulong totalHdd = 0;
foreach (var storageJson in devicesWithStorage)
{
try
{
var drives = JsonSerializer.Deserialize<List<StorageDeviceInfo>>(storageJson!);
if (drives == null) continue;
totalSsd += (ulong)drives.Where(d => d.MediaType == "SSD").Sum(d => (long)d.Size);
totalHdd += (ulong)drives.Where(d => d.MediaType == "HDD").Sum(d => (long)d.Size);
}
catch { /* Ignore deserialization errors */ }
}
return Results.Ok(new { TotalSsdBytes = totalSsd, TotalHddBytes = totalHdd });
});
dashboardApi.MapGet("/admin-accounts", async (InventoryContext db) =>
{
var devicesWithAdmins = await db.Devices
.AsNoTracking()
.Where(d => !string.IsNullOrEmpty(d.LocalAdmins))
.Select(d => d.LocalAdmins)
.ToListAsync();
var adminCounts = new Dictionary<string, int>();
foreach (var adminJson in devicesWithAdmins)
{
try
{
var admins = JsonSerializer.Deserialize<List<LocalAdminInfo>>(adminJson!);
if (admins == null) continue;
foreach (var admin in admins.Where(a => !string.IsNullOrWhiteSpace(a.Name)))
{
adminCounts.TryGetValue(admin.Name!, out var currentCount);
adminCounts[admin.Name!] = currentCount + 1;
}
}
catch { /* Ignore deserialization errors */ }
}
var topAdmins = adminCounts.OrderByDescending(kv => kv.Value)
.Take(5)
.Select(kv => new { AccountName = kv.Key, Count = kv.Value });
return Results.Ok(topAdmins);
});
// --- Device Endpoints ---
devicesApi.MapGet("/", async (InventoryContext db, string? deviceType, string? processor, string? ram, string? location, string? sortBy, bool sortAscending = true, int page = 1, int pageSize = 25) =>
{
var query = db.Devices.AsNoTracking();
// Filtering
if (!string.IsNullOrWhiteSpace(deviceType)) query = query.Where(d => d.DeviceType == deviceType);
if (!string.IsNullOrWhiteSpace(processor)) query = query.Where(d => d.Processor != null && d.Processor.Contains(processor));
if (!string.IsNullOrWhiteSpace(ram)) query = query.Where(d => d.RAM == ram);
if (!string.IsNullOrWhiteSpace(location)) query = query.Where(d => d.Location != null && d.Location.Contains(location));
// Sorting
if (!string.IsNullOrWhiteSpace(sortBy))
{
// This is a simplified sort. A more robust solution would use reflection or a dictionary.
query = sortBy.ToLowerInvariant() switch
{
"computername" => sortAscending ? query.OrderBy(d => d.ComputerName) : query.OrderByDescending(d => d.ComputerName),
"devicetype" => sortAscending ? query.OrderBy(d => d.DeviceType) : query.OrderByDescending(d => d.DeviceType),
"location" => sortAscending ? query.OrderBy(d => d.Location) : query.OrderByDescending(d => d.Location),
"lastseen" => sortAscending ? query.OrderBy(d => d.LastSeen) : query.OrderByDescending(d => d.LastSeen),
_ => query.OrderBy(d => d.Id)
};
}
else
{
query = query.OrderBy(d => d.Id);
}
var totalCount = await query.CountAsync();
var items = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
return Results.Ok(new PaginatedResult<Device>
{
PageNumber = page,
PageSize = pageSize,
TotalCount = totalCount,
Items = items
});
});
devicesApi.MapGet("/{id:int}", async (InventoryContext db, int id) =>
{
var device = await db.Devices.AsNoTracking().FirstOrDefaultAsync(d => d.Id == id);
return device is not null ? Results.Ok(device) : Results.NotFound();
});
// --- Export Endpoints ---
async Task<IQueryable<Device>> GetFilteredDevicesQuery(InventoryContext db, string? deviceType, string? processor, string? ram, string? location)
{
var query = db.Devices.AsNoTracking();
if (!string.IsNullOrWhiteSpace(deviceType)) query = query.Where(d => d.DeviceType == deviceType);
if (!string.IsNullOrWhiteSpace(processor)) query = query.Where(d => d.Processor != null && d.Processor.Contains(processor));
if (!string.IsNullOrWhiteSpace(ram)) query = query.Where(d => d.RAM == ram);
if (!string.IsNullOrWhiteSpace(location)) query = query.Where(d => d.Location != null && d.Location.Contains(location));
return await Task.FromResult(query.OrderBy(d => d.ComputerName));
}
exportApi.MapGet("/excel", async (InventoryContext db, [FromQuery] string? deviceType, [FromQuery] string? processor, [FromQuery] string? ram, [FromQuery] string? location) =>
{
var query = await GetFilteredDevicesQuery(db, deviceType, processor, ram, location);
var devices = await query.ToListAsync();
var fileBytes = ReportGenerator.GenerateExcel(devices);
return Results.File(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "DeviceInventory.xlsx");
});
exportApi.MapGet("/pdf", async (InventoryContext db, [FromQuery] string? deviceType, [FromQuery] string? processor, [FromQuery] string? ram, [FromQuery] string? location) =>
{
var query = await GetFilteredDevicesQuery(db, deviceType, processor, ram, location);
var devices = await query.ToListAsync();
var fileBytes = ReportGenerator.GeneratePdf(devices);
return Results.File(fileBytes, "application/pdf", "DeviceInventory.pdf");
});
app.Run();