added app insights

This commit is contained in:
Oliver Kieselbach
2026-03-19 22:04:59 +01:00
parent 9cd6669811
commit dc2aa913f6
24 changed files with 322 additions and 6 deletions
+1
View File
@@ -106,6 +106,7 @@ tmpclaude-*-cwd
.claude/commands/backend-logs.md
.claude/commands/session-debug.md
.claude/commands/table-query.md
.claude/commands/ux-insights.md
.claude/scripts/query-appinsights.sh
.claude/scripts/query-table.sh
@@ -8,6 +8,7 @@ import { useTenant } from "../../contexts/TenantContext";
import { useAuth } from "../../contexts/AuthContext";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { downloadAsJson, stripInternalFields, bumpVersion } from "@/lib/rulePageHelpers";
import { StatCard } from "@/components/rules/StatCard";
import { RuleFilterBar } from "@/components/rules/RuleFilterBar";
@@ -26,6 +27,8 @@ interface TenantInfo {
export default function AnalyzeRulesPage() {
const router = useRouter();
useEffect(() => { trackEvent("analyze_rules_viewed"); }, []);
const { tenantId } = useTenant();
const { user, getAccessToken } = useAuth();
@@ -161,6 +164,7 @@ export default function AnalyzeRulesPage() {
{ method: "DELETE" }
);
if (result !== null) {
trackEvent("rule_deleted", { ruleType: "analyze" });
setRules((prev) => (prev || []).filter((r) => r.ruleId !== rule.ruleId));
if (expandedRuleId === rule.ruleId) setExpandedRuleId(null);
showSuccess(`Rule "${rule.title}" deleted successfully!`);
@@ -202,6 +206,7 @@ export default function AnalyzeRulesPage() {
}
);
if (result !== null) {
trackEvent("rule_created", { ruleType: "analyze" });
showSuccess(`Rule "${form.title}" created successfully!`);
setNewRule({ ...EMPTY_FORM, conditions: [{ ...EMPTY_CONDITION }] });
setShowCreateForm(false);
@@ -252,6 +257,7 @@ export default function AnalyzeRulesPage() {
}
);
if (result !== null) {
trackEvent("rule_modified", { ruleType: "analyze" });
setEditingRuleId(null);
showSuccess(`Rule "${editForm.title}" updated successfully!`);
await fetchRules();
@@ -8,6 +8,7 @@ import { useNotifications } from '../../contexts/NotificationContext';
import { ProtectedRoute } from '../../components/ProtectedRoute';
import { API_BASE_URL } from '@/lib/config';
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
interface AuditLogEntry {
id: string;
@@ -25,6 +26,8 @@ type EntityTypeFilter = string;
export default function AuditPage() {
const router = useRouter();
useEffect(() => { trackEvent("audit_log_viewed"); }, []);
const { tenantId } = useTenant();
const { getAccessToken } = useAuth();
const { addNotification } = useNotifications();
@@ -3,6 +3,7 @@
import { useRouter } from "next/navigation";
import { useState, useEffect, useRef } from "react";
import { Session } from "../types";
import { trackEvent } from "@/lib/appInsights";
// Column definition for the session table
interface ColumnDef {
@@ -353,7 +354,7 @@ export function SessionTable({
return (
<button
key={status}
onClick={() => onStatusFilterChange(isActive ? null : status)}
onClick={() => { if (!isActive) trackEvent("session_filter_applied", { filterType: "status", value: status }); onStatusFilterChange(isActive ? null : status); }}
className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold border transition-colors cursor-pointer ${isActive ? c.bgActive : c.bg}`}
>
{c.label}
@@ -449,7 +450,7 @@ export function SessionTable({
paginatedSessions.map((session) => (
<tr
key={session.sessionId}
onClick={() => router.push(`/sessions/${session.sessionId}`)}
onClick={() => { trackEvent("session_opened", { sessionId: session.sessionId, status: session.status ?? "" }); router.push(`/sessions/${session.sessionId}`); }}
className="hover:bg-gray-50 cursor-pointer transition-colors"
>
{activeColumns.map((col) => (
@@ -10,6 +10,7 @@ import { useAuth } from "../../contexts/AuthContext";
import { useNotifications } from "../../contexts/NotificationContext";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { Session } from "./types";
import { StatsCard } from "./components/StatsCards";
import { WelcomeMessage } from "./components/WelcomeMessage";
@@ -349,6 +350,14 @@ export default function Home() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isConnected]);
// Track search usage (debounced — fires 1s after user stops typing)
useEffect(() => {
const timer = setTimeout(() => {
trackEvent("session_searched", { hasQuery: searchQuery.length > 0 });
}, 1000);
return () => clearTimeout(timer);
}, [searchQuery]);
// Save admin mode to localStorage when it changes
useEffect(() => {
localStorage.setItem('adminMode', adminMode.toString());
@@ -469,6 +478,7 @@ export default function Home() {
});
if (response.ok) {
trackEvent("session_deleted", { inAdminMode: adminMode });
setSessions(prevSessions => prevSessions.filter(s => s.sessionId !== sessionToDelete.sessionId));
console.log(`Session ${sessionToDelete.sessionId} deleted successfully`);
setShowDeleteConfirm(false);
@@ -9,6 +9,7 @@ import { useNotifications } from "../../../contexts/NotificationContext";
import { ProtectedRoute } from "../../../components/ProtectedRoute";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { ConfidenceBadge, SeverityBadge } from "./components/DiagnosisBadges";
import { Session, EnrollmentEvent, RuleResult } from "@/types";
@@ -16,6 +17,8 @@ export default function DiagnosisPage() {
const params = useParams();
const router = useRouter();
const sessionId = params?.sessionId as string;
useEffect(() => { trackEvent("diagnosis_viewed", { sessionId: sessionId ?? "" }); }, [sessionId]);
const [session, setSession] = useState<Session | null>(null);
const [sessionTenantId, setSessionTenantId] = useState<string | null>(null);
const [events, setEvents] = useState<EnrollmentEvent[]>([]);
@@ -9,6 +9,7 @@ import { useAuth } from "../../contexts/AuthContext";
import { useNotifications } from "../../contexts/NotificationContext";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import FleetStatCard from "./components/FleetStatCard";
import { Session } from "@/types";
@@ -34,6 +35,8 @@ interface AppMetricsResponse {
export default function FleetHealthPage() {
const router = useRouter();
useEffect(() => { trackEvent("fleet_health_viewed"); }, []);
const [sessions, setSessions] = useState<Session[]>([]);
const [appMetrics, setAppMetrics] = useState<AppMetricsResponse | null>(null);
const [loading, setLoading] = useState(true);
@@ -8,6 +8,7 @@ import { useTenant } from "../../contexts/TenantContext";
import { useAuth } from "../../contexts/AuthContext";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { downloadAsJson, stripInternalFields, bumpVersion } from "@/lib/rulePageHelpers";
import { StatCard } from "@/components/rules/StatCard";
import { RuleFilterBar } from "@/components/rules/RuleFilterBar";
@@ -25,6 +26,8 @@ interface TenantInfo {
export default function GatherRulesPage() {
const router = useRouter();
useEffect(() => { trackEvent("gather_rules_viewed"); }, []);
const { tenantId } = useTenant();
const { user, getAccessToken } = useAuth();
@@ -182,6 +185,7 @@ export default function GatherRulesPage() {
{ method: "DELETE" }
);
if (result !== null) {
trackEvent("rule_deleted", { ruleType: "gather" });
setRules(prev => (prev || []).filter(r => r.ruleId !== rule.ruleId));
if (expandedRuleId === rule.ruleId) setExpandedRuleId(null);
showSuccess(`Rule "${rule.title}" deleted`);
@@ -255,6 +259,7 @@ export default function GatherRulesPage() {
}
);
if (result !== null) {
trackEvent("rule_created", { ruleType: "gather" });
showSuccess(`Rule "${form.title}" created`);
setShowCreateForm(false);
setNewRule({ ...EMPTY_FORM });
@@ -338,6 +343,7 @@ export default function GatherRulesPage() {
}
);
if (result !== null) {
trackEvent("rule_modified", { ruleType: "gather" });
showSuccess(`Rule "${form.title}" saved`);
setEditingRuleId(null);
setJsonModeEdit(false);
@@ -8,6 +8,7 @@ import { useTenant } from "../../contexts/TenantContext";
import { useAuth } from "../../contexts/AuthContext";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
// Dynamically import the map component (Leaflet requires window/document)
const GeoMap = dynamic(() => import("./GeoMap"), { ssr: false });
@@ -91,6 +92,8 @@ const formatBytes = (bytes: number) => {
export default function GeographicPerformancePage() {
const router = useRouter();
useEffect(() => { trackEvent("geographic_performance_viewed"); }, []);
const [geoMetrics, setGeoMetrics] = useState<GeographicMetricsResponse | null>(null);
const [loading, setLoading] = useState(true);
const [timeRange, setTimeRange] = useState<TimeRange>("30d");
@@ -14,9 +14,12 @@ import { EmptyState } from "@/components/rules/EmptyState";
import { ImeLogPattern, PatternForm, EMPTY_FORM, ACTION_LABELS } from "./types";
import ImePatternCard from "./components/ImePatternCard";
import { trackEvent } from "@/lib/appInsights";
export default function ImeLogPatternsPage() {
const router = useRouter();
useEffect(() => { trackEvent("ime_patterns_viewed"); }, []);
const { tenantId } = useTenant();
const { user } = useAuth();
const isGalacticAdmin = user?.isGalacticAdmin ?? false;
@@ -152,6 +155,7 @@ export default function ImeLogPatternsPage() {
: p
)
);
trackEvent("ime_pattern_modified");
setEditingPatternId(null);
showSuccess(`Pattern "${patternId}" updated successfully!`);
}
@@ -12,6 +12,7 @@ import ScrollToTopButton from "../components/ScrollToTopButton";
import FeedbackBubble from "../components/FeedbackBubble";
import { SidebarProvider } from "../contexts/SidebarContext";
import { GlobalSidebar } from "../components/GlobalSidebar";
import AppInsightsInit from "../components/AppInsightsInit";
const inter = Inter({ subsets: ["latin"] });
@@ -121,6 +122,7 @@ export default function RootLayout({
<TenantProvider>
<SignalRProvider>
<SidebarProvider>
<AppInsightsInit />
<Navbar />
<GlobalSidebar>
{children}
@@ -7,6 +7,7 @@ import { ProtectedRoute } from '../../components/ProtectedRoute';
import { useAuth } from '../../contexts/AuthContext';
import { useNotifications } from '../../contexts/NotificationContext';
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
// ── Types ──────────────────────────────────────────────────────────────────────
@@ -94,6 +95,8 @@ function pN(values: number[], percentile: number): number {
export default function PlatformMetricsPage() {
const router = useRouter();
useEffect(() => { trackEvent("platform_metrics_viewed"); }, []);
const { getAccessToken, user } = useAuth();
const { addNotification } = useNotifications();
@@ -6,6 +6,7 @@ import { API_BASE_URL } from '@/lib/config';
import { ProtectedRoute } from '../../components/ProtectedRoute';
import { useAuth } from '../../contexts/AuthContext';
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
interface SessionMetrics {
total: number;
@@ -83,6 +84,8 @@ interface PlatformUsageMetrics {
export default function PlatformUsageMetricsPage() {
const router = useRouter();
useEffect(() => { trackEvent("platform_usage_metrics_viewed"); }, []);
const { getAccessToken } = useAuth();
const [metrics, setMetrics] = useState<PlatformUsageMetrics | null>(null);
const [loading, setLoading] = useState(true);
@@ -16,6 +16,7 @@ export default function PrivacyPage() {
<li>Azure AD/Entra ID tenant information</li>
<li>User authentication information (UPN, display name, tenant ID)</li>
<li>Operational telemetry and audit logs</li>
<li>Anonymized usage telemetry (via Azure Application Insights, no cookies) — used to understand which features are used and improve the service. No personal data is transmitted.</li>
</ul>
<h2 className="text-xl font-semibold text-gray-900 mt-6">Data Processing Context</h2>
@@ -3,6 +3,7 @@
import { useState, useEffect, useRef, useMemo } from "react";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { useTenant } from "../../contexts/TenantContext";
import { useAuth } from "../../contexts/AuthContext";
import { useNotifications } from "../../contexts/NotificationContext";
@@ -32,6 +33,8 @@ const phaseSteps = [
];
export default function ProgressPortalPage() {
useEffect(() => { trackEvent("progress_portal_viewed"); }, []);
const [serialInput, setSerialInput] = useState("");
const [session, setSession] = useState<Session | null>(null);
const [allSessions, setAllSessions] = useState<Session[]>([]);
@@ -194,6 +197,7 @@ export default function ProgressPortalPage() {
const searchBySerial = async () => {
if (!serialInput.trim()) return;
trackEvent("progress_serial_submitted");
setSearching(true);
setSearched(true);
setNotFound(false);
@@ -28,6 +28,7 @@ import { InformationCircleIcon, ComputerDesktopIcon, PlayCircleIcon, SparklesIco
import DeviceDetailsCard from "./components/DeviceDetailsCard";
import { generateUiExport, generateCsvExport, generateSessionCsvExport, generateRuleResultsCsvExport, SessionExportEvent } from "@/lib/sessionExportUtils";
import { Session, EnrollmentEvent, RuleResult } from "@/types";
import { trackEvent } from "@/lib/appInsights";
const GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -35,6 +36,8 @@ export default function SessionDetailPage() {
const params = useParams();
const router = useRouter();
const sessionId = params?.sessionId as string;
useEffect(() => { trackEvent("session_detail_viewed", { sessionId: sessionId ?? "" }); }, [sessionId]);
const [events, setEvents] = useState<EnrollmentEvent[]>([]);
const [session, setSession] = useState<Session | null>(null);
const [sessionTenantId, setSessionTenantId] = useState<string | null>(null);
@@ -950,7 +953,7 @@ export default function SessionDetailPage() {
loadingAnalysis={loadingAnalysis}
analysisExpanded={analysisExpanded}
setAnalysisExpanded={setAnalysisExpanded}
onReanalyze={() => fetchAnalysisResults(true)}
onReanalyze={() => { trackEvent("analyze_now_clicked", { sessionId: sessionId ?? "" }); fetchAnalysisResults(true); }}
/>
</div>
)}
@@ -8,6 +8,7 @@ import { useAuth } from "../../contexts/AuthContext";
import { useNotifications } from "../../contexts/NotificationContext";
import { API_BASE_URL } from "@/lib/config";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { usePageSections } from "../../hooks/usePageSections";
import { PageSectionItem } from "../../contexts/SidebarContext";
import { ShieldCheckIcon, UsersIcon, CpuChipIcon, GearIcon, MagnifyingGlassIcon, LockClosedIcon, BellIcon, CloudArrowUpIcon, KeyIcon, CircleStackIcon, ArrowRightOnRectangleIcon } from "../../lib/sidebarIcons";
@@ -26,6 +27,8 @@ import { TenantConfiguration, TenantAdmin, DiagnosticsLogPath } from "./types";
export default function SettingsPage() {
const router = useRouter();
useEffect(() => { trackEvent("settings_viewed"); }, []);
const { tenantId } = useTenant();
const { getAccessToken, user, logout } = useAuth();
const { addNotification } = useNotifications();
@@ -8,6 +8,7 @@ import { useNotifications } from '../../contexts/NotificationContext';
import { ProtectedRoute } from '../../components/ProtectedRoute';
import { API_BASE_URL } from '@/lib/config';
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
interface TenantInfo {
tenantId: string;
@@ -78,6 +79,8 @@ interface PlatformUsageMetrics {
export default function UsageMetricsPage() {
const router = useRouter();
useEffect(() => { trackEvent("usage_metrics_viewed"); }, []);
const { tenantId } = useTenant();
const { getAccessToken, user } = useAuth();
const { addNotification } = useNotifications();
@@ -0,0 +1,31 @@
"use client";
import { useEffect } from "react";
import { useAuth } from "@/contexts/AuthContext";
import { useTenant } from "@/contexts/TenantContext";
import { useTheme } from "@/contexts/ThemeContext";
import { initAppInsights, setTelemetryContext } from "@/lib/appInsights";
export default function AppInsightsInit() {
const connectionString = process.env.NEXT_PUBLIC_APPINSIGHTS_CONNECTION_STRING ?? "";
const { user } = useAuth();
const { tenantId } = useTenant();
const { theme } = useTheme();
useEffect(() => {
if (connectionString) {
initAppInsights(connectionString);
}
}, [connectionString]);
useEffect(() => {
setTelemetryContext(
tenantId || null,
user?.isTenantAdmin ?? false,
user?.isGalacticAdmin ?? false,
theme
);
}, [tenantId, user, theme]);
return null;
}
@@ -8,6 +8,7 @@ import { useState, useRef, useEffect } from 'react';
import Link from 'next/link';
import { API_BASE_URL } from '@/lib/config';
import { usePathname } from 'next/navigation';
import { trackEvent } from '@/lib/appInsights';
export default function Navbar() {
const { isAuthenticated, user, logout } = useAuth();
@@ -409,7 +410,7 @@ export default function Navbar() {
<span className="text-sm text-gray-700">Admin Mode</span>
{adminMode && <span className="text-[10px] text-amber-600 font-semibold">ON</span>}
</div>
<button onClick={() => setAdminMode(!adminMode)} className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${adminMode ? 'bg-amber-500' : 'bg-gray-300'}`}>
<button onClick={() => { trackEvent("admin_mode_toggled", { enabled: !adminMode }); setAdminMode(!adminMode); }} className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${adminMode ? 'bg-amber-500' : 'bg-gray-300'}`}>
<span className={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${adminMode ? 'translate-x-[18px]' : 'translate-x-[3px]'}`} />
</button>
</div>
@@ -427,7 +428,7 @@ export default function Navbar() {
<span className="text-sm text-gray-700">Galactic Admin</span>
{galacticAdminMode && <span className="text-[10px] text-purple-700 font-semibold">ON</span>}
</div>
<button onClick={() => setGalacticAdminMode(!galacticAdminMode)} className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${galacticAdminMode ? 'bg-purple-600' : 'bg-gray-300'}`}>
<button onClick={() => { trackEvent("admin_mode_toggled", { enabled: !galacticAdminMode, isGalactic: true }); setGalacticAdminMode(!galacticAdminMode); }} className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${galacticAdminMode ? 'bg-purple-600' : 'bg-gray-300'}`}>
<span className={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${galacticAdminMode ? 'translate-x-[18px]' : 'translate-x-[3px]'}`} />
</button>
</div>
@@ -0,0 +1,52 @@
import { ApplicationInsights } from "@microsoft/applicationinsights-web";
let appInsights: ApplicationInsights | null = null;
const telemetryConfig = {
tenantId: null as string | null,
isAdmin: false,
isGalacticAdmin: false,
theme: "light" as "light" | "dark",
};
export function initAppInsights(connectionString: string) {
if (appInsights || !connectionString || typeof window === "undefined") return;
appInsights = new ApplicationInsights({
config: {
connectionString,
disableCookiesUsage: true,
enableAutoRouteTracking: true,
disableFetchTracking: false,
},
});
appInsights.addTelemetryInitializer((envelope) => {
envelope.data = envelope.data ?? {};
if (telemetryConfig.tenantId) envelope.data["tenantId"] = telemetryConfig.tenantId;
envelope.data["isAdmin"] = telemetryConfig.isAdmin;
envelope.data["isGalacticAdmin"] = telemetryConfig.isGalacticAdmin;
envelope.data["theme"] = telemetryConfig.theme;
});
appInsights.loadAppInsights();
}
export function setTelemetryContext(
tenantId: string | null,
isAdmin: boolean,
isGalacticAdmin: boolean,
theme: "light" | "dark"
) {
telemetryConfig.tenantId = tenantId;
telemetryConfig.isAdmin = isAdmin;
telemetryConfig.isGalacticAdmin = isGalacticAdmin;
telemetryConfig.theme = theme;
}
export function trackEvent(
name: string,
properties?: Record<string, string | number | boolean>
) {
appInsights?.trackEvent({ name }, properties);
}
+169
View File
@@ -10,6 +10,7 @@
"dependencies": {
"@azure/msal-browser": "^3.28.0",
"@azure/msal-react": "^2.1.3",
"@microsoft/applicationinsights-web": "^3.3.11",
"@microsoft/signalr": "^10.0.0",
"@types/leaflet": "^1.9.21",
"fflate": "^0.8.2",
@@ -815,6 +816,159 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@microsoft/applicationinsights-analytics-js": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-3.3.11.tgz",
"integrity": "sha512-33fS5Y7uPgckP8yWqCAoWpWerA+d3RUC2lv6kkfJQM/V2KHeN4CGDtyXXWpq5vSZjE9BESXao13iBq4wiWit8Q==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-common": "3.3.11",
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-cfgsync-js": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-cfgsync-js/-/applicationinsights-cfgsync-js-3.3.11.tgz",
"integrity": "sha512-HdBc/ldMZpqGtzAFDs49o82wTdKRX2B0BzmjgS+D4MrS7CLth4MxLHJHOpmTI2bmc/S4T8n32LlFkaQCvWb9ig==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-common": "3.3.11",
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-async": ">= 0.5.4 < 2.x",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-channel-js": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.11.tgz",
"integrity": "sha512-0ex/mxTf5R+P5WSvdU8Hgbeg8VzQ0XvcnjKQdmQy05ycScnKevt8an3DEPikOFqOKDi59L5hUETZlcdhesnVtg==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-common": "3.3.11",
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-async": ">= 0.5.4 < 2.x",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-common": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.11.tgz",
"integrity": "sha512-OIe5vL56lkmIsRsI21QqbGpF8gF/UzUP4mlEhGWyG2UMskdtWrY+c+xAynyNDsAjhKKge+Rrs/xkpC0Fo0QrhQ==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-core-js": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.11.tgz",
"integrity": "sha512-WlBY1sKDNL62T++NifgFCyDuOoNUNrVILfnHubOzgU/od7MFEQYWU8EZyDcBC/+Z8e3TD6jfixurYtWoUC+6Eg==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-async": ">= 0.5.4 < 2.x",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-dependencies-js": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-3.3.11.tgz",
"integrity": "sha512-+D7vBHvLWFveZmg1PCbDhpw/tiD+/AnptzEoV8IsM9wPbiHPGHdni9IDMrTJZdJZVafO0GxnxOJNpir8QTdO8g==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-common": "3.3.11",
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-async": ">= 0.5.4 < 2.x",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-properties-js": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-3.3.11.tgz",
"integrity": "sha512-GOyIV8QziFop/LelZW1LAKwhGvtjzN7d47NkSRh9ZyZPW/ahWifakdQ6mZfLcL2uTyTmpcK8JtMtfNADo77NZg==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-common": "3.3.11",
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/applicationinsights-shims": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz",
"integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==",
"license": "MIT",
"dependencies": {
"@nevware21/ts-utils": ">= 0.9.4 < 2.x"
}
},
"node_modules/@microsoft/applicationinsights-web": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-3.3.11.tgz",
"integrity": "sha512-LEI6LhfLFzBfPJ4j5TTPNC8LxwDpiJZSeyD2VVU3EPN2RtK+15vEL0oONaSV/AsWnDrwH3DIku0Nm+EVRJ4pNQ==",
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-analytics-js": "3.3.11",
"@microsoft/applicationinsights-cfgsync-js": "3.3.11",
"@microsoft/applicationinsights-channel-js": "3.3.11",
"@microsoft/applicationinsights-common": "3.3.11",
"@microsoft/applicationinsights-core-js": "3.3.11",
"@microsoft/applicationinsights-dependencies-js": "3.3.11",
"@microsoft/applicationinsights-properties-js": "3.3.11",
"@microsoft/applicationinsights-shims": "3.0.1",
"@microsoft/dynamicproto-js": "^2.0.3",
"@nevware21/ts-async": ">= 0.5.4 < 2.x",
"@nevware21/ts-utils": ">= 0.11.8 < 2.x"
},
"peerDependencies": {
"tslib": ">= 1.0.0"
}
},
"node_modules/@microsoft/dynamicproto-js": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz",
"integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==",
"license": "MIT",
"dependencies": {
"@nevware21/ts-utils": ">= 0.10.4 < 2.x"
}
},
"node_modules/@microsoft/signalr": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-10.0.0.tgz",
@@ -841,6 +995,21 @@
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@nevware21/ts-async": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.5.tgz",
"integrity": "sha512-vwqaL05iJPjLeh5igPi8MeeAu10i+Aq7xko1fbo9F5Si6MnVN5505qaV7AhSdk5MCBJVT/UYMk3kgInNjDb4Ig==",
"license": "MIT",
"dependencies": {
"@nevware21/ts-utils": ">= 0.12.2 < 2.x"
}
},
"node_modules/@nevware21/ts-utils": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz",
"integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==",
"license": "MIT"
},
"node_modules/@next/env": {
"version": "15.5.12",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz",
@@ -11,6 +11,7 @@
"dependencies": {
"@azure/msal-browser": "^3.28.0",
"@azure/msal-react": "^2.1.3",
"@microsoft/applicationinsights-web": "^3.3.11",
"@microsoft/signalr": "^10.0.0",
"@types/leaflet": "^1.9.21",
"fflate": "^0.8.2",
File diff suppressed because one or more lines are too long