All checks were successful
Trigger Cloudarix Deploy / call-webhook (push) Successful in 2s
Implemented Tasks T038-T046: - T038: Created useCopyToClipboard hook with toast notifications - T039: Skipped (unit tests - optional) - T040: Added copy button for Policy ID field - T041: Added copy button for Setting Name field - T042: Added tabs for Details and Raw JSON views - T043: Implemented Raw JSON tab with syntax highlighting - T044: Created getIntunePortalLink utility (8 policy types) - T045: Added Open in Intune button with URL construction - T046: Fallback to copy Policy ID if URL unavailable Files Created: - lib/hooks/useCopyToClipboard.ts (65 lines) - lib/utils/policy-table-helpers.ts (127 lines) Files Updated: - components/policy-explorer/PolicyDetailSheet.tsx (enhanced with tabs, copy buttons, Intune links) Features: - Copy-to-clipboard for all fields with visual feedback - Two-tab interface: Details (enhanced fields) and Raw JSON (full object) - Deep linking to Intune portal by policy type - Clipboard API with document.execCommand fallback - Toast notifications for user feedback
116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
/**
|
|
* Policy Table Helper Functions
|
|
*
|
|
* Utilities for formatting, sorting, and generating links for policy data.
|
|
*/
|
|
|
|
import type { PolicySettingRow } from '@/lib/types/policy-table';
|
|
|
|
/**
|
|
* Generate Intune Portal URL for a policy based on its type and ID
|
|
* @param policyType The type of policy (e.g., 'deviceConfiguration')
|
|
* @param graphPolicyId The Microsoft Graph policy ID
|
|
* @returns Intune Portal URL or null if URL construction fails
|
|
*/
|
|
export function getIntunePortalLink(
|
|
policyType: string,
|
|
graphPolicyId: string
|
|
): string | null {
|
|
if (!graphPolicyId) {
|
|
return null;
|
|
}
|
|
|
|
const baseUrl = 'https://intune.microsoft.com';
|
|
|
|
// Map policy types to Intune portal paths
|
|
const policyTypeUrls: Record<string, string> = {
|
|
// Device Configuration
|
|
deviceConfiguration: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceConfigProfilesMenu/~/properties/policyId/${graphPolicyId}`,
|
|
|
|
// Compliance Policies
|
|
compliancePolicy: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceCompliancePoliciesMenu/~/properties/policyId/${graphPolicyId}`,
|
|
|
|
// Device Management Scripts
|
|
deviceManagementScript: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceManagementScriptsMenu/~/properties/scriptId/${graphPolicyId}`,
|
|
|
|
// Windows Update for Business
|
|
windowsUpdateForBusiness: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/UpdateRingsMenu/~/properties/policyId/${graphPolicyId}`,
|
|
|
|
// iOS Update Configuration
|
|
iosUpdateConfiguration: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/iOSUpdateConfigurationsMenu/~/properties/policyId/${graphPolicyId}`,
|
|
|
|
// Settings Catalog
|
|
settingsCatalog: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/ConfigurationPolicyMenu/~/properties/policyId/${graphPolicyId}`,
|
|
|
|
// Endpoint Protection
|
|
endpointProtection: `${baseUrl}/#view/Microsoft_Intune_Workflows/SecurityBaselinesSummaryMenu/~/properties/templateId/${graphPolicyId}`,
|
|
|
|
// macOS Extensions
|
|
macOSExtensionsConfiguration: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceConfigProfilesMenu/~/properties/policyId/${graphPolicyId}`,
|
|
};
|
|
|
|
// Check if we have a known URL pattern
|
|
if (policyType in policyTypeUrls) {
|
|
return policyTypeUrls[policyType];
|
|
}
|
|
|
|
// Fallback: Generic device settings URL
|
|
return `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceSettingsMenu`;
|
|
}
|
|
|
|
/**
|
|
* Format policy type for display (convert camelCase to Title Case)
|
|
* @param policyType Policy type in camelCase (e.g., 'deviceConfiguration')
|
|
* @returns Formatted title (e.g., 'Device Configuration')
|
|
*/
|
|
export function formatPolicyType(policyType: string): string {
|
|
// Insert spaces before capital letters
|
|
const withSpaces = policyType.replace(/([A-Z])/g, ' $1');
|
|
|
|
// Capitalize first letter and trim
|
|
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1).trim();
|
|
}
|
|
|
|
/**
|
|
* Truncate long text with ellipsis
|
|
* @param text Text to truncate
|
|
* @param maxLength Maximum length before truncation
|
|
* @returns Truncated text with ellipsis if needed
|
|
*/
|
|
export function truncateText(text: string, maxLength: number = 100): string {
|
|
if (text.length <= maxLength) {
|
|
return text;
|
|
}
|
|
return text.substring(0, maxLength) + '...';
|
|
}
|
|
|
|
/**
|
|
* Format date for display (relative or absolute)
|
|
* @param date Date to format
|
|
* @param relative Whether to show relative time (e.g., "2 hours ago")
|
|
* @returns Formatted date string
|
|
*/
|
|
export function formatDate(date: Date | string, relative: boolean = false): string {
|
|
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
|
|
if (relative) {
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - dateObj.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
|
|
if (diffMins < 1) return 'just now';
|
|
if (diffMins < 60) return `${diffMins} min${diffMins > 1 ? 's' : ''} ago`;
|
|
|
|
const diffHours = Math.floor(diffMins / 60);
|
|
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
|
|
const diffDays = Math.floor(diffHours / 24);
|
|
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
|
|
const diffWeeks = Math.floor(diffDays / 7);
|
|
return `${diffWeeks} week${diffWeeks > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
return dateObj.toLocaleString();
|
|
}
|