shoc-backend/SeaHavenIndustries/Components/Pages/Home.razor
Adam Moussa c887d6d9d8 fix(security): remove hardcoded secrets from source
Replace all hardcoded credentials with configuration-injected values:
- SQL Server connection strings -> ${CONNECTION_STRING} env-var placeholders (4 appsettings files)
- SendGrid API keys -> ${SENDGRID_API_KEY} (incl. commented copies in SendMessage.cs)
- JWT signing secret -> ${JWT_SECRET} (3 appsettings files)
- AWS access key pair in UploadFileHp.cs -> DI-injected IAmazonS3 (SDK default credential chain)
- Google Maps API keys in App.razor / Home.razor -> IConfiguration lookup
- Legacy SMTP credentials in SendMessage.cs comments -> placeholders

Add .env.example documenting required environment variables and a
Configuration & Secrets section in BACKEND_ARCHITECTURE.md.

All exposed credentials were rotated 2026-06-05 prior to this scrub.
Source: github-audit-report.md Criticals 1-2 (Agent A4).
Verified: dotnet build 0 errors; secret-pattern grep clean.
2026-06-05 11:56:54 -04:00

587 lines
27 KiB
Text

@page "/"
<Header pagename="Home" icon='<i class="material-icons-round opacity-10">home</i>'></Header>
<style>
.rz-progressbar-circular-md {
width: 5rem;
height: 5rem;
}
</style>
<AuthorizeView Roles="Admin,Manager">
<Authorized>
<div class="row">
<div class="col-md-8 col-lg-8 position-relative mt-3">
<div class="" style="background-image:url('./assets/img/bg.png'); background-size: cover; background-position: center;height: 140px; width:100%; background-repeat:no-repeat; border-radius:10px;">
<div class="text-white px-4 py-3">
<span class="d-block mb-2 fw-bold">
Welcome Back,
<img src="./assets/img/waving-hand-svgrepo-com.png" alt="Waving Hand" style="height: 24px; margin-left: 5px;" />
</span>
<span class="d-block fw-bold text-info">John Doe,</span>
<span class="d-block text-sm">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.</span>
</div>
</div>
</div>
<div class="col-md-4 col-lg-4 px-2 py-3">
<div class="card">
<div class="row row px-3 py-3">
<div class="col-7">
<span class="d-block fw-bold text-xs">In Progress Work Orders</span>
<span class="d-block fw-bolder pt-3" style="font-size: 22px;">@DashboardViewModel.Inprogress</span>
</div>
<div class="col-5">
<img class="img-fluid pt-3" src="./assets/img/Progress-Chart-Sparkline.png" width="72" />
</div>
</div>
<div class="d-flex px-2 py-2">
<img src="./assets/img/arrow-icon.png" class="me-2" />
<p class="mb-0 text-sm"> <span class="text-white"></span> Updated Now </p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 pt-3">
<div class="card">
<div class="row g-3 px-3 py-3">
<div class="col-12 col-md d-flex align-items-center justify-content-center">
<div class="me-3">
<RadzenProgressBarCircular ProgressBarStyle="ProgressBarStyle.Success" @bind-Value="@DashboardViewModel.Sev1Per" />
</div>
<h6 class="mt-2 text-center">
<span>@DashboardViewModel.Sev1</span>
<span class="d-block text-body text-sm">Sev 1</span>
</h6>
</div>
<div class="col-12 col-md d-flex align-items-center justify-content-center">
<div class="me-3">
<RadzenProgressBarCircular ProgressBarStyle="ProgressBarStyle.Primary" @bind-Value="@DashboardViewModel.Sev2Per" />
</div>
<h6 class="mt-2 text-center">
<span>@DashboardViewModel.Sev2</span>
<span class="d-block text-body text-sm">Sev 2</span>
</h6>
</div>
<div class="col-12 col-md d-flex align-items-center justify-content-center">
<div class="me-3">
<RadzenProgressBarCircular ProgressBarStyle="ProgressBarStyle.Info" @bind-Value="@DashboardViewModel.Sev3Per" />
</div>
<h6 class="mt-2 text-center">
<span>@DashboardViewModel.Sev3</span>
<span class="d-block text-body text-sm">Sev 3</span>
</h6>
</div>
<div class="col-12 col-md d-flex align-items-center justify-content-center">
<div class="me-3">
<RadzenProgressBarCircular ProgressBarStyle="ProgressBarStyle.Warning" @bind-Value="@DashboardViewModel.Sev4Per" />
</div>
<h6 class="mt-2 text-center">
<span>@DashboardViewModel.Sev4</span>
<span class="d-block text-body text-sm">Sev 4</span>
</h6>
</div>
<div class="col-12 col-md d-flex align-items-center justify-content-center">
<div class="me-3">
<RadzenProgressBarCircular ProgressBarStyle="ProgressBarStyle.Danger" @bind-Value="@DashboardViewModel.Sev5Per" />
</div>
<h6 class="mt-2 text-center">
<span>@DashboardViewModel.Sev5</span>
<span class="d-block text-body text-sm">Sev 5</span>
</h6>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-lg-4 pt-3">
<div class="card">
<div class="row row px-3 py-3">
<div class="col-7">
<span class="d-block fw-bold text-xs">Completed Work Orders</span>
<span class="d-block fw-bolder pt-3" style="font-size: 22px;">@DashboardViewModel.Completed</span>
</div>
<div class="col-5">
<img class="img-fluid pt-3" src="./assets/img/Complete-Chart-Sparkline.png" width="72" />
</div>
</div>
<div class="d-flex px-2 py-2">
<img src="./assets/img/arrow-icon.png" class="me-2" />
<p class="mb-0 text-sm"> <span class="text-white"> </span> Updated Now </p>
</div>
</div>
</div>
<div class="col-md-4 col-lg-4 px-2 py-3">
<div class="card">
<div class="row row px-3 py-3">
<div class="col-7">
<span class="d-block fw-bold text-xs">Open Work Orders</span>
<span class="d-block fw-bolder pt-3" style="font-size: 22px;">@DashboardViewModel.Open</span>
</div>
<div class="col-5">
<img class="img-fluid pt-3" src="./assets/img/Open-Chart-Sparkline.png" width="72" />
</div>
</div>
<div class="d-flex px-2 py-2">
<img src="./assets/img/arrow-icon.png" class="me-2" />
<p class="mb-0 text-sm"> <span class="text-white"></span> Updated Now </p>
</div>
</div>
</div>
<div class="col-md-4 col-lg-4 pt-3">
<div class="card">
<div class="row row px-3 py-3">
<div class="col-7">
<span class="d-block fw-bold text-xs">On Hold Work Orders</span>
<span class="d-block fw-bolder pt-3" style="font-size: 22px;">@DashboardViewModel.Onhold</span>
</div>
<div class="col-5">
<img class="img-fluid pt-3" src="./assets/img/Hold-Chart-Sparkline.png" width="72" />
</div>
</div>
<div class="d-flex px-2 py-2">
<img src="./assets/img/arrow-icon.png" class="me-2" />
<p class="mb-0 text-sm"> <span class="text-white"></span> Updated Now </p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 px-2 pb-3">
<div class="card mt-3">
<div class="card-header p-3 pt-2">
<div class="d-block d-md-flex">
<div class="me-auto">
<h6 class="mb-0" style="opacity:0.6 !important;">Work Order Status</h6>
</div>
</div>
</div>
<div class="card-body p-3 pt-0">
<div class="chart">
<canvas id="pChart" class="chart-canvas" height="331"></canvas>
</div>
</div>
</div>
</div>
@* <div class="col-md-3 col-lg-3 px-2 py-3">
<div class="card h-100">
<div class="card-header pb-0 p-3">
<h6 class="mb-0">Orders</h6>
</div>
<div class="card-body mb-5">
<div class="row">
<div class="col-12 text-center">
<div class="chart">
<canvas id="chart-consumption" class="chart-canvas" height="190" @ref="chartCanvas"></canvas>
</div>
<h4 class="font-weight-bold mt-n8">
<span class="d-block text-body text-sm">Total</span>
<span>@DashboardViewModel.Total</span>
</h4>
</div>
</div>
</div>
<div class="card-footer p-3">
<div class="d-flex">
<span class="badge badge-md badge-dot me-2 d-block text-start">
<i class="bg-info"></i>
<span class="text-xs">Open</span>
</span>
<span class="badge badge-md badge-dot me-2 d-block text-start">
<i class="bg-danger"></i>
<span class="text-xs">On Hold</span>
</span>
<span class="badge badge-md badge-dot me-2 d-block text-start">
<i class="bg-dark"></i>
<span class="text-xs">In Progress</span>
</span>
<span class="badge badge-md badge-dot me-2 d-block text-start">
<i class="bg-warning"></i>
<span class="text-xs">Done</span>
</span>
</div>
</div>
</div>
</div>
*@ <div class="col-md-4 py-3">
<div class="card">
<h6 class="px-2 py-2" style="opacity:0.6 !important;">Work Orders</h6>
<div class="">
<table class="table align-items-center ">
<tbody>
@if (DashboardViewModel != null && DashboardViewModel.Dashboardworkorders != null)
{
foreach (var itm in DashboardViewModel.Dashboardworkorders)
{
var userNameInitials = "";
if (itm.UserName != null && !string.IsNullOrEmpty(itm.UserName))
{
var nameParts = itm.UserName.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (nameParts.Length > 1)
{
// Take the first letter of the first two words
userNameInitials = string.Concat(nameParts[0][0], nameParts[1][0]).ToUpper();
}
else
{
// Take the first letter of the single word
userNameInitials = nameParts[0][0].ToString().ToUpper() + nameParts[0][1].ToString().ToUpper();
}
}
<tr>
<td class="w-80">
<div class="d-flex px-2 py-1 align-items-center">
<div>
@if (userNameInitials != "" && userNameInitials != null)
{
<span class="text-xs" style="border-radius:50%;background: #7dcced;color: black;padding:8px">@userNameInitials</span>
}
else
{
<span class="text-xs" style="border-radius:50%;background: #7dcced;color: black;padding:8px">NA</span>
}
</div>
<div class="ms-4">
<p class="text-xs font-weight-bold mb-0 " style=" max-width: 180px; overflow: hidden; text-overflow: ellipsis;">@itm.Name</p>
</div>
</div>
</td>
<td class="w-20">
<div class="text-end">
@if (itm.Status == "Open")
{
<div class="btn btn-sm btn-outline-success ">Open</div>
}
else if (itm.Status == "On Hold")
{
<div class="btn btn-sm btn-outline-warning ">On Hold</div>
}
else if (itm.Status == "In Progress")
{
<div class="btn btn-sm btn-outline-danger">In Progress</div>
}
else if (itm.Status == "Done")
{
<div class="btn btn-sm btn-outline-info ">Done</div>
}
</div>
</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card">
<div class="row">
<div class="col-6">
<h6 class="px-4 pt-3" style="opacity:0.6 !important;">Map</h6>
<p class="px-4 pb-3 fst-italic">Lorem ipsum is a dummy text</p>
</div>
<div class="col-6">
<div class="input-group input-group-outline px-4 pt-3 pb-3">
<RadzenTextBox style="width: 100%; " @ref="addressBoxRadzenTextBox" Name="BusinessAddress1" value="@mapval" Change=@GoogleAddressAutoComplete />
</div>
</div>
<div class="col-12 px-5 mb-4">
<RadzenGoogleMap ApiKey="@Configuration["GoogleMaps:ApiKey"]" @ref=map style="height: 400px" Options=@(new Dictionary<string, object> { { "disableDoubleClickZoom", true } })
Zoom=@zoom Center=@(new GoogleMapPosition() { Lat = 42.6977, Lng = 23.3219 }) MapClick=@OnMapClick>
<Markers>
@foreach (var itm in setmappins)
{
<RadzenGoogleMapMarker Title="@itm.title" Label="@itm.title" Position=@(new GoogleMapPosition() { Lat = itm.lat, Lng = itm.lon }) />
}
<RadzenGoogleMapMarker Title="Paris " Label="Paris" Position=@(new GoogleMapPosition() { Lat = 48.8566, Lng = 2.3522 }) />
@if (showMadridMarker)
{
<RadzenGoogleMapMarker Title="Khayaban e Sir Syed, Rawalpindi, Punjab, Pakistan " Label="Madrid" Position=@(new GoogleMapPosition() { Lat = 40.4168, Lng = -3.7038 }) />
}
</Markers>
</RadzenGoogleMap>
</div>
</div>
</div>
</Authorized>
</AuthorizeView>
@if (isloading)
{
<div class="loading">Loading&#8230;</div>
}
@using GoogleMapsComponents.Maps.Places
@using Microsoft.AspNetCore.Authorization
@using Radzen
@using Radzen.Blazor
@using Microsoft.JSInterop
@using System.Text.Json
@using System.Globalization
@using SeaHavenIndustries.Data.IRepository
@using SeaHavenIndustries.Helper.ViewModels
@using global::Data.SeaHavenIndustries
@inject IJSRuntime JSRuntime
@attribute [Authorize]
@inject NavigationManager Navigation
@inject IWorkOrderRepository _service;
@inject IAccountRepository _aservice;
@inject ISettingsRepository _Seservice;
@inject HttpClient Http
@inject IConfiguration Configuration
@code {
private IEnumerable<ApplicationUser>? users { get; set; }
private bool isloading = false;
private IEnumerable<LocationViewModel>? droplocations { get; set; }
private IEnumerable<LocationViewModel>? locationslist { get; set; }
private DashboardViewModel DashboardViewModel = new DashboardViewModel();
private ElementReference chartCanvas;
protected override async Task OnInitializedAsync()
{
isloading = true;
await Task.Delay(50);
DashboardViewModel = await _Seservice.Getdashboarddata();
isloading = false;
}
RadzenDataGrid<Locations> grid;
int count;
int eid;
private ElementReference addressBox;
private RadzenTextBox? addressBoxRadzenTextBox;
private Autocomplete? autocomplete;
private string? placeData;
private string? formattedAddress;
IEnumerable<Locations> locations;
List<setmappin> setmappins = new List<setmappin>();
bool isLoading = false;
private Locations model = new Locations();
string mapval = "";
List<string> titles = new List<string> { "Sales Representative", "Vice President, Sales", "Sales Manager", "Inside Sales Coordinator" };
IEnumerable<string> selectedTitles;
int zoom = 3;
bool showMadridMarker;
// EventConsole console;
RadzenGoogleMap map;
void OnMapClick(GoogleMapClickEventArgs args)
{
// Check if any marker was clicked
var clickedMarker = setmappins.FirstOrDefault(marker =>
Math.Round(marker.lat, 4) == Math.Round(args.Position.Lat, 4) &&
Math.Round(marker.lon, 4) == Math.Round(args.Position.Lng, 4));
if (clickedMarker != null)
{
// Access the title of the clicked marker
string clickedTitle = clickedMarker.title;
// Perform actions based on the clicked title
Console.WriteLine($"Marker with title '{clickedTitle}' clicked.");
// Add additional logic as needed
}
else
{
// No marker was clicked directly on the map
Console.WriteLine("No marker clicked directly on the map.");
}
}
// async Task OnMarkerClick(RadzenGoogleMapMarker marker)
// {
// var message = $"Custom information about <b>{marker.Title}</b>";
// var code = $@"
// var map = Radzen['{map.UniqueID}'].instance;
// var marker = map.markers.find(m => m.title == '{marker.Title}');
// if(window.infoWindow) {{window.infoWindow.close();}}
// window.infoWindow = new google.maps.InfoWindow({{content: '{message}'}});
// setTimeout(() => window.infoWindow.open(map, marker), 200);
// ";
// await JSRuntime.InvokeVoidAsync("eval", code);
// }
async Task OnSelectedTitlesChange(object value)
{
if (selectedTitles != null && !selectedTitles.Any())
{
selectedTitles = null;
}
await grid.FirstPage();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("initScripts");
addressBox = addressBoxRadzenTextBox.Element;
await SetupGoogleAddressAutoComplete();
await EnsureDataLoadedAndInvokeJS();
}
}
private async Task EnsureDataLoadedAndInvokeJS()
{
while (DashboardViewModel.Sev1 == null || DashboardViewModel.Sev1Per == null)
{
await Task.Delay(10);
}
// Once data is loaded, call the JavaScript function
await JSRuntime.InvokeVoidAsync("CircleGraph", DashboardViewModel.Sev1, DashboardViewModel.Sev1Per);
await JSRuntime.InvokeVoidAsync("CircleGraph2", DashboardViewModel.Sev2, DashboardViewModel.Sev2Per);
await JSRuntime.InvokeVoidAsync("CircleGraph3", DashboardViewModel.Sev3, DashboardViewModel.Sev3Per);
await JSRuntime.InvokeVoidAsync("CircleGraph4", DashboardViewModel.Sev4, DashboardViewModel.Sev4Per);
await JSRuntime.InvokeVoidAsync("CircleGraph5", DashboardViewModel.Sev5, DashboardViewModel.Sev5Per);
List<int> list = new List<int>();
list.Add(DashboardViewModel.Open.Value);
list.Add(DashboardViewModel.Onhold.Value);
list.Add(DashboardViewModel.Inprogress.Value);
list.Add(DashboardViewModel.Completed.Value);
//await JSRuntime.InvokeVoidAsync("ConsumptionGraph", list);
await JSRuntime.InvokeVoidAsync("PerformanceChartHome", list);
}
protected async Task UpdateChart()
{
await JSRuntime.InvokeVoidAsync("initScripts");
addressBox = addressBoxRadzenTextBox.Element;
await SetupGoogleAddressAutoComplete();
await JSRuntime.InvokeVoidAsync("ConsumptionGraph");
await JSRuntime.InvokeVoidAsync("CircleGraph", DashboardViewModel.Sev1, DashboardViewModel.Sev1Per);
await JSRuntime.InvokeVoidAsync("CircleGraph2");
await JSRuntime.InvokeVoidAsync("PerformanceChartHome");
}
private async Task GoogleAddressAutoComplete()
{
System.Threading.Thread.Sleep(500); // this is a hack. with no wait place was null first time
var place = await this.autocomplete.GetPlace();
var placeDataTest = JsonSerializer.Serialize(place);
if (place?.AddressComponents == null)
{
// this.message = "No results available for " + place?.Name;
}
else
{
var placesd = await this.autocomplete.GetPlace();
model.Name = place.FormattedAddress;
var placeId = place.PlaceId;
var details = await GetLatLng(placeId);
var streetNumber = place.AddressComponents.FirstOrDefault(a => a.Types != null && a.Types.Contains("street_number"))?.LongName;
var street = place.AddressComponents.FirstOrDefault(a => a.Types != null && (a.Types.Contains("route") || a.Types.Contains("street_address")))?.LongName;
var city = place.AddressComponents.FirstOrDefault(a => a.Types != null && (a.Types.Contains("locality") || a.Types.Contains("neighborhood")))?.LongName;
var state = place.AddressComponents.FirstOrDefault(a => a.Types != null && a.Types.Contains("administrative_area_level_1"))?.LongName;
var country = place.AddressComponents.FirstOrDefault(a => a.Types != null && a.Types.Contains("country"))?.LongName;
var postcode = place.AddressComponents.FirstOrDefault(a => a.Types != null && a.Types.Contains("postal_code"))?.LongName;
formattedAddress = place.FormattedAddress;
// model.Title = place.AddressComponents.FirstOrDefault()?.LongName;
//setmappins.Add(new setmappin{title=place.Name,lat=place.l})
placeData = JsonSerializer.Serialize(place);
}
}
private async Task<(double Latitude, double Longitude)> GetLatLng(string placeId)
{
var apiKey = Configuration["GoogleMaps:ApiKey"]; // Injected from configuration (env var GoogleMaps__ApiKey / user-secrets)
//var apiUrl = $"https://places.googleapis.com/v1/places/{placeId}";
var apiUrl = $"https://maps.googleapis.com/maps/api/place/autocomplete/json?input=Vict&language=pt_BR&types=%28cities%29&key={apiKey}";
// var apiUrl = $"https://places.googleapis.com/v1/places/{placeId}?fields=id,displayName&key={apiKey}";
try
{
var response = await Http.GetFromJsonAsync<GeocodingApiResponse>(apiUrl);
if (response?.status == "OK" && response.results?.Count > 0)
{
var location = response.results[0].geometry?.location;
if (location != null)
{
return (location.lat, location.lng);
}
}
Console.WriteLine($"Error retrieving geocode. Status: {response?.status}");
return default;
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching geocode: {ex.Message}");
return default;
}
}
public class GeocodingApiResponse
{
public string status { get; set; }
public List<GeocodingResult> results { get; set; }
}
public class GeocodingResult
{
public Geometry geometry { get; set; }
}
public class Geometry
{
public Location location { get; set; }
}
public class Location
{
public double lat { get; set; }
public double lng { get; set; }
}
private async Task SetupGoogleAddressAutoComplete()
{
this.autocomplete = await Autocomplete.CreateAsync(JSRuntime, addressBox, new AutocompleteOptions
{
StrictBounds = false,
});
await this.autocomplete.SetFields(new[] { "address_components", "place_id", "formatted_address" });
}
@inject NorthwindService service
public void Export(string type)
{
service.Export("locations", type, new Query()
{
OrderBy = grid.Query.OrderBy,
Filter = grid.Query.Filter,
Select = string.Join(",", grid.ColumnsCollection.Where(c => c.GetVisible() && !string.IsNullOrEmpty(c.Property))
.Select(c => c.Property.Contains(".") ? $"{c.Property} as {c.Property.Replace(".", "_")}" : c.Property))
});
}
public class setmappin
{
public string title { get; set; }
public double lat { get; set; }
public double lon { get; set; }
}
}