Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
{
"permissions": {
"allow": [
"mcp__dual-graph__graph_scan",
"mcp__dual-graph__graph_continue",
"Bash(find G:ResgridResgridCoreResgrid.ModelBilling -type f -name *.cs)",
"Bash(python3:*)",
"mcp__dual-graph__graph_read",
"Bash(dotnet build:*)",
"mcp__dual-graph__graph_register_edit",
"mcp__dual-graph__graph_scan",
"Bash(find /g/Resgrid/Resgrid -type d \\\\\\(-name *mobile* -o -name *Mobile* -o -name *app* -o -name *App* -o -name *apps* -o -name *Apps* \\\\\\))",
"Bash(xargs grep:*)",
"Bash(grep -E \"\\\\.cs$|Program\")",
"Bash(grep -l \"DepartmentMembers\\\\|DepartmentMember\" \"G:\\\\Resgrid\\\\Resgrid/Repositories/Resgrid.Repositories.DataRepository/\"*.cs)",
"Bash(find /g/Resgrid/Resgrid/Repositories/Resgrid.Repositories.DataRepository/Servers -name *.cs)",
"Bash(find G:/Resgrid/Resgrid -newer G:/Resgrid/Resgrid/CLAUDE.md -name *.cs -not -path */obj/* -not -path */.git/*)",
"Bash(dotnet build:*)"
"Bash(grep -r \"mapbox\\\\|react-native\\\\|rnmapbox\" /g/Resgrid/Resgrid --include=package.json --include=*.ts --include=*.tsx)",
"Bash(find G:ResgridResgridWebResgrid.WebAreasUserViews -type f -name *.cshtml)",
"Bash(grep -r \"google.maps\\\\|mapboxgl\\\\|leaflet\\\\|openstreetmap\" G:/Resgrid/Resgrid/Web/Resgrid.Web/Areas/User/Apps/src --include=*.ts)",
"Bash(grep -r \"leaflet\\\\|L\\\\.tileLayer\\\\|OpenStreetMap\" /g/Resgrid/Resgrid/Web/Resgrid.Web.Services --include=*.cs)",
"Bash(find /g/Resgrid/Resgrid -type f -name *.swift -o -name *.kt -o -name *.java)"
]
},
"hooks": {
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,4 @@ Web/Resgrid.WebCore/wwwroot/lib/*
.dual-graph/
.claude/settings.local.json
.claude/settings.local.json
/Web/Resgrid.Web/wwwroot/js/ng/chunks
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether generated entries reference chunk paths.
rg -n --glob '!**/node_modules/**' 'chunks/|js/ng/chunks|react-elements\.js' Web/Resgrid.Web Web/Resgrid.Web/Areas/User/Apps 2>/dev/null || true

# Confirm git ignores the new chunk directory.
git check-ignore -v Web/Resgrid.Web/wwwroot/js/ng/chunks/example.js || true

# Inspect build/publish config for copying Vite output into wwwroot/js/ng.
fd -i '^(vite\.config\.(ts|js)|package\.json|.*\.csproj|.*\.targets|.*\.props|Directory\.Build\.(props|targets))$' \
  -x sh -c '
    printf "\n--- %s ---\n" "$1"
    rg -n "Areas/User/Apps|wwwroot/js/ng|react-elements|vite|chunks|outDir" "$1" || true
  ' sh {}

Repository: Resgrid/Core

Length of output: 50370


🏁 Script executed:

#!/bin/bash
# Read the vite.config.ts to see output configuration
echo "=== vite.config.ts ===" 
cat Web/Resgrid.Web/Areas/User/Apps/vite.config.ts

echo ""
echo "=== Contents of wwwroot/js/ng/ ==="
ls -la Web/Resgrid.Web/wwwroot/js/ng/ 2>/dev/null || echo "Directory not found"

echo ""
echo "=== Contents of wwwroot/js/ng/chunks/ ==="
ls -la Web/Resgrid.Web/wwwroot/js/ng/chunks/ 2>/dev/null || echo "Chunks directory not found or empty"

echo ""
echo "=== Check if chunks directory is listed in .gitignore ==="
grep -n "chunks" .gitignore || echo "No explicit chunks entry in .gitignore"

Repository: Resgrid/Core

Length of output: 1810


🏁 Script executed:

#!/bin/bash
# Search for build/publish configuration that copies dist/core to wwwroot
echo "=== Searching for build scripts/targets ==="
fd -i '\.(csproj|targets|props|json)$' Web/Resgrid.Web Web/Resgrid.Web/Areas/User/Apps \
  | head -20 | xargs rg -l 'dist/core|wwwroot|copy|build.*chunk' 2>/dev/null

echo ""
echo "=== Check .csproj for publish/build configuration ==="
find Web/Resgrid.Web -name "*.csproj" -type f | head -1 | xargs rg -A5 -B5 'dist|www|publish|copy' 2>/dev/null | head -50

echo ""
echo "=== Check package.json for build/publish scripts ==="
rg -A20 '"scripts"' Web/Resgrid.Web/Areas/User/Apps/package.json 2>/dev/null || echo "Not found"

echo ""
echo "=== Verify if dist/core exists and has chunks ==="
ls -la Web/Resgrid.Web/Areas/User/Apps/dist/ 2>/dev/null || echo "dist/ not found"
ls -la Web/Resgrid.Web/Areas/User/Apps/dist/core/ 2>/dev/null || echo "dist/core/ not found"
ls -la Web/Resgrid.Web/Areas/User/Apps/dist/core/chunks/ 2>/dev/null || echo "dist/core/chunks/ not found"

Repository: Resgrid/Core

Length of output: 3097


Confirm the build/publish pipeline copies chunks from dist/core/ to wwwroot/js/ng/.

The vite config outputs chunks to dist/core/chunks/, but the entry point react-elements.js in wwwroot/js/ng/ imports them with relative paths (import "./chunks/elements-BnCXm89z.js"). The chunks directory is not present in wwwroot and is correctly ignored by git as a generated artifact. However, no build step in the .csproj or npm scripts explicitly copies the compiled chunks to the deployed location. Without this copy step, the entry file will fail at runtime with 404 errors when browsers attempt to load the chunk files.

Ensure the ASP.NET publish or build process includes a step that copies dist/core/chunks/ to wwwroot/js/ng/chunks/ (or verify this happens in your CI/CD pipeline).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 277, The Vite-generated chunk files in dist/core/chunks
are not being copied to the deployed wwwroot/js/ng/chunks so react-elements.js
imports will 404; fix by adding an explicit copy step either in the project
build or npm pipeline: update the .csproj to include an MSBuild Target (e.g.,
AfterPublish or BeforePublish) that copies dist/core/chunks/** to
wwwroot/js/ng/chunks/**, or add a npm script (postbuild) that copies
dist/core/chunks to wwwroot/js/ng/chunks before publish; reference the artifact
paths (dist/core/chunks, wwwroot/js/ng/chunks) and the entry file
react-elements.js so the copy happens as part of the build/publish for CI/CD.

281 changes: 257 additions & 24 deletions Core/Resgrid.Config/MappingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace Resgrid.Config
using System;

namespace Resgrid.Config
{
public static class MappingConfig
{
public const string LeafletMapProvider = "leaflet";
public const string MapboxMapProvider = "mapbox";

public static int PersonnelLocationStaleSeconds = 30;
public static int UnitLocationStaleSeconds = 30;
public static int PersonnelLocationMinMeters = 20;
Expand Down Expand Up @@ -53,64 +58,292 @@ public static class MappingConfig
public static string BigBoardOSMKey = "";

public static string DispatchAppMapboxKey = "";
public static string WebsiteMapboxKey = "";
public static string WebsiteMapboxAccessToken = "";
public static string WebsiteMapMode = LeafletMapProvider;

public static string LeafletTileUrl = "https://api.maptiler.com/maps/streets/{{z}}/{{x}}/{{y}}.png?key={0}";
public static string MapBoxTileUrl = "";
public static string MapBoxStyleUrl = "";

public static string LeafletAttribution = "© OpenStreetMap contributors CC-BY-SA";
public static string MapBoxAttribution = "© Mapbox © OpenStreetMap contributors";

/***********************************
* Geocoding and Routing Service URLs
***********************************/
public static string NominatimUrl = "https://nominatim.openstreetmap.org";
public static string OsrmUrl = "https://router.project-osrm.org";

public static ResolvedMapConfig GetMapConfig(string key)
{
var surfaceKey = string.IsNullOrWhiteSpace(key) ? InfoConfig.WebsiteKey : key;
var mapProvider = GetPreferredMapProvider(surfaceKey);

if (mapProvider == MapboxMapProvider)
{
var mapboxAccessToken = NormalizePublicMapboxAccessToken(GetSystemMapboxAccessToken(surfaceKey));

if (TryCreateMapboxConfig(MapBoxStyleUrl, mapboxAccessToken, false, out var mapboxConfig))
return mapboxConfig;

if (!string.IsNullOrWhiteSpace(mapboxAccessToken) && !string.IsNullOrWhiteSpace(MapBoxTileUrl))
{
return new ResolvedMapConfig
{
MapProvider = MapboxMapProvider,
TileUrl = ReplaceTileKey(MapBoxTileUrl, mapboxAccessToken),
StyleUrl = MapBoxStyleUrl,
AccessToken = mapboxAccessToken,
Attribution = MapBoxAttribution,
IsDepartmentOverride = false
};
}
}

return new ResolvedMapConfig
{
MapProvider = LeafletMapProvider,
TileUrl = GetLegacyLeafletUrl(surfaceKey),
StyleUrl = string.Empty,
AccessToken = string.Empty,
Attribution = LeafletAttribution,
IsDepartmentOverride = false
};
}

public static bool TryCreateMapboxConfig(string styleUrl, string accessToken, bool isDepartmentOverride, out ResolvedMapConfig mapConfig)
{
mapConfig = null;
var publicAccessToken = NormalizePublicMapboxAccessToken(accessToken);

if (string.IsNullOrWhiteSpace(styleUrl) || string.IsNullOrWhiteSpace(publicAccessToken))
return false;

var styleId = GetMapboxStyleId(styleUrl);

if (string.IsNullOrWhiteSpace(styleId))
return false;

mapConfig = new ResolvedMapConfig
{
MapProvider = MapboxMapProvider,
TileUrl = $"https://api.mapbox.com/styles/v1/{styleId}/tiles/256/{{z}}/{{x}}/{{y}}@2x?access_token={publicAccessToken}",
StyleUrl = NormalizeMapboxStyleUrl(styleUrl, styleId),
AccessToken = publicAccessToken,
Attribution = MapBoxAttribution,
IsDepartmentOverride = isDepartmentOverride
};

return true;
}

public static bool IsSupportedMapboxStyleUrl(string styleUrl)
{
return !string.IsNullOrWhiteSpace(GetMapboxStyleId(styleUrl));
}

public static string GetWebsiteOSMUrl()
{
if (!string.IsNullOrWhiteSpace(WebsiteOSMKey))
return string.Format(MapBoxTileUrl, WebsiteOSMKey);
else
return LeafletTileUrl;
return GetMapConfig(InfoConfig.WebsiteKey).TileUrl;
}

public static string GetApiOSMUrl()
{
if (!string.IsNullOrWhiteSpace(ApiOSMKey))
return string.Format(LeafletTileUrl, ApiOSMKey);
else
return LeafletTileUrl;
return GetMapConfig(InfoConfig.ApiKey).TileUrl;
}

public static string GetResponderAppOSMUrl()
{
if (!string.IsNullOrWhiteSpace(ResponderAppOSMKey))
return string.Format(LeafletTileUrl, ResponderAppOSMKey);
else
return LeafletTileUrl;
return GetMapConfig(InfoConfig.ResponderAppKey).TileUrl;
}

public static string GetUnitAppOSMUrl()
{
if (!string.IsNullOrWhiteSpace(UnitAppOSMKey))
return string.Format(LeafletTileUrl, UnitAppOSMKey);
else
return LeafletTileUrl;
return GetMapConfig(InfoConfig.UnitAppKey).TileUrl;
}

public static string GetBigBoardAppOSMUrl()
{
if (!string.IsNullOrWhiteSpace(BigBoardOSMKey))
return string.Format(LeafletTileUrl, BigBoardOSMKey);
else
return LeafletTileUrl;
return GetMapConfig(InfoConfig.BigBoardKey).TileUrl;
}

public static string GetDispatchAppOSMUrl()
{
if (!string.IsNullOrWhiteSpace(DispatchAppMapboxKey))
return string.Format(MapBoxTileUrl, DispatchAppMapboxKey);
else
return GetMapConfig(InfoConfig.DispatchAppKey).TileUrl;
}

private static string GetLegacyLeafletUrl(string key)
{
if (key == InfoConfig.WebsiteKey)
{
if (!string.IsNullOrWhiteSpace(WebsiteOSMKey))
return ReplaceTileKey(LeafletTileUrl, WebsiteOSMKey);

return LeafletTileUrl;
}

if (key == InfoConfig.ApiKey)
{
if (!string.IsNullOrWhiteSpace(ApiOSMKey))
return ReplaceTileKey(LeafletTileUrl, ApiOSMKey);

return LeafletTileUrl;
}

if (key == InfoConfig.ResponderAppKey)
{
if (!string.IsNullOrWhiteSpace(ResponderAppOSMKey))
return ReplaceTileKey(LeafletTileUrl, ResponderAppOSMKey);

return LeafletTileUrl;
}

if (key == InfoConfig.UnitAppKey)
{
if (!string.IsNullOrWhiteSpace(UnitAppOSMKey))
return ReplaceTileKey(LeafletTileUrl, UnitAppOSMKey);

return LeafletTileUrl;
}

if (key == InfoConfig.BigBoardKey)
{
if (!string.IsNullOrWhiteSpace(BigBoardOSMKey))
return ReplaceTileKey(LeafletTileUrl, BigBoardOSMKey);

return LeafletTileUrl;
}

if (key == InfoConfig.DispatchAppKey)
{
if (!string.IsNullOrWhiteSpace(DispatchAppOSMKey))
return ReplaceTileKey(LeafletTileUrl, DispatchAppOSMKey);

return LeafletTileUrl;
}

return LeafletTileUrl;
}

private static string GetPreferredMapProvider(string key)
{
if (key == InfoConfig.WebsiteKey)
return NormalizeMapProvider(WebsiteMapMode);

if (key == InfoConfig.DispatchAppKey && !string.IsNullOrWhiteSpace(DispatchAppMapboxKey))
return MapboxMapProvider;

if (key == InfoConfig.UnitAppKey && !string.IsNullOrWhiteSpace(UnitAppMapBoxKey))
return MapboxMapProvider;

return LeafletMapProvider;
}

private static string GetSystemMapboxAccessToken(string key)
{
if (key == InfoConfig.WebsiteKey)
{
if (!string.IsNullOrWhiteSpace(WebsiteMapboxAccessToken))
return WebsiteMapboxAccessToken;

if (!string.IsNullOrWhiteSpace(WebsiteMapboxKey))
return WebsiteMapboxKey;

return WebsiteOSMKey;
}

if (key == InfoConfig.DispatchAppKey)
return DispatchAppMapboxKey;

if (key == InfoConfig.UnitAppKey)
return UnitAppMapBoxKey;

return string.Empty;
}

private static string NormalizeMapProvider(string provider)
{
if (string.IsNullOrWhiteSpace(provider))
return LeafletMapProvider;

return provider.Trim().Equals(MapboxMapProvider, StringComparison.InvariantCultureIgnoreCase)
? MapboxMapProvider
: LeafletMapProvider;
}

private static string ReplaceTileKey(string tileUrl, string key)
{
if (string.IsNullOrWhiteSpace(tileUrl))
return tileUrl;

var normalizedTileUrl = tileUrl
.Replace("{{", "{", StringComparison.InvariantCulture)
.Replace("}}", "}", StringComparison.InvariantCulture);

if (string.IsNullOrWhiteSpace(key))
return normalizedTileUrl;

return normalizedTileUrl.Replace("{0}", key, StringComparison.InvariantCulture);
}

private static string NormalizeMapboxStyleUrl(string styleUrl, string styleId)
{
if (styleUrl.StartsWith("mapbox://styles/", StringComparison.InvariantCultureIgnoreCase))
return styleUrl;

return $"mapbox://styles/{styleId}";
}

private static string NormalizePublicMapboxAccessToken(string accessToken)
{
if (string.IsNullOrWhiteSpace(accessToken))
return string.Empty;

var trimmedAccessToken = accessToken.Trim();

return trimmedAccessToken.StartsWith("pk.", StringComparison.Ordinal)
? trimmedAccessToken
: string.Empty;
}

private static string GetMapboxStyleId(string styleUrl)
{
if (string.IsNullOrWhiteSpace(styleUrl))
return null;

var trimmedStyleUrl = styleUrl.Trim();

if (trimmedStyleUrl.StartsWith("mapbox://styles/", StringComparison.InvariantCultureIgnoreCase))
return ExtractMapboxStyleId(trimmedStyleUrl.Substring("mapbox://styles/".Length));

if (Uri.TryCreate(trimmedStyleUrl, UriKind.Absolute, out var mapboxStyleUri))
{
var path = mapboxStyleUri.AbsolutePath.Trim('/');
var stylesIndex = path.IndexOf("styles/v1/", StringComparison.InvariantCultureIgnoreCase);

if (stylesIndex >= 0)
{
var stylePath = path.Substring(stylesIndex + "styles/v1/".Length);
return ExtractMapboxStyleId(stylePath);
}
}

return null;
}

private static string ExtractMapboxStyleId(string stylePath)
{
if (string.IsNullOrWhiteSpace(stylePath))
return null;

var normalizedPath = stylePath.Trim('/');
var pathSegments = normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);

if (pathSegments.Length < 2)
return null;

return $"{pathSegments[0]}/{pathSegments[1]}";
}
}
}
1 change: 1 addition & 0 deletions Core/Resgrid.Config/NumberProviderConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public static class NumberProviderConfig
public static string TwilioResgridNumber = "";
public static string TwilioApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/IncomingMessage";
public static string TwilioVoiceCallApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/VoiceCall?userId={0}&callId={1}";
public static string TwilioVoiceVerificationApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/VoiceVerification?userId={0}&contactType={1}";
public static string TwilioVoiceApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/InboundVoice";

// Diafaan (https://www.diafaan.com)
Expand Down
17 changes: 17 additions & 0 deletions Core/Resgrid.Config/ResolvedMapConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Resgrid.Config
{
public class ResolvedMapConfig
{
public string MapProvider { get; set; }

public string TileUrl { get; set; }

public string StyleUrl { get; set; }

public string AccessToken { get; set; }

public string Attribution { get; set; }

public bool IsDepartmentOverride { get; set; }
}
}
Loading
Loading