besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
1,532 lines (1,378 loc) • 68.5 kB
JavaScript
/**
* Comprehensive Account Management with Workspace Hierarchy and APIM Dataverse Integration
* Implements user impersonation, workspace hierarchy management, and related entity processing
* Follows developer guide requirements for structured format with parent account relationships
*/
class AccountManagementPage {
constructor(options = {}) {
this.options = {
containerId: 'besper-site-content',
environment: 'prod',
...options,
};
this.initialized = false;
// Comprehensive workspace management system
this.currentWorkspace = null;
this.workspaces = [];
this.isEditMode = false;
this.originalValues = {};
this.apiBaseUrl = this.getApiBaseUrl();
this.userToken = null;
this.userId = null;
// BULLETPROOF: Defer all complex operations to avoid blocking class export
this.authService = null;
this.operatorsService = null;
this.isAuthenticated = false;
this.authInitialized = false;
this.authInitializing = false;
// Initialize auth safely in next tick to ensure class export completes first
if (typeof window !== 'undefined') {
setTimeout(() => {
this.initializeAuth();
}, 1);
}
}
/**
* Get API base URL for Dataverse operations via APIM
*/
getApiBaseUrl() {
// Determine the correct API base URL based on environment
const hostname = window.location.hostname;
if (hostname.includes('localhost') || hostname.includes('127.0.0.1')) {
return 'http://localhost:7071/api';
} else {
// Use APIM internal endpoint for administrative operations
const envMatch = hostname.match(/(\w+)\.powerapp/);
const environment = envMatch ? envMatch[1] : 'dev';
return `https://apim${environment}0926internal.azure-api.net/api/admin`;
}
}
/**
* Initialize authentication safely without blocking class export
*/
initializeAuth() {
// Prevent multiple simultaneous auth initializations
if (this.authInitialized || this.authInitializing) {
console.log(
'[WorkspaceManagement] Auth already initialized or initializing'
);
return;
}
this.authInitializing = true;
try {
this.authService = this.getAuthService();
this.operatorsService = this.getOperatorsService();
// Initialize workspace authentication for APIM Dataverse operations
this.initializeWorkspaceAuth();
this.authInitialized = true;
} catch (error) {
console.warn('Auth initialization failed, using fallback:', error);
this.authService = this.createFallbackAuthService();
this.operatorsService = this.createFallbackOperatorsService();
this.authInitialized = true;
} finally {
this.authInitializing = false;
}
}
/**
* Initialize authentication for workspace management with APIM Dataverse
*/
async initializeWorkspaceAuth() {
try {
console.log(
'[WorkspaceManagement] Initializing workspace authentication with PROFESSIONAL cookie-based tokens...'
);
// Use PROFESSIONAL cookie-based token manager (hyper-secure and infinite-loop-proof)
if (window.professionalTokenManager) {
try {
const token = await window.professionalTokenManager.getToken(
'WorkspaceManagement'
);
if (token) {
this.userToken = token;
// Get user info from professional token manager
const userInfo = window.professionalTokenManager.getUserInfo();
this.userId = userInfo.contactId || userInfo.userId || 'unknown';
console.log(
'[WorkspaceManagement] [SECURITY] Authentication initialized with PROFESSIONAL tokens, user:',
this.userId
);
this.isAuthenticated = true;
return;
}
} catch (error) {
console.warn(
'[WorkspaceManagement] Professional token manager failed:',
error
);
}
}
// Fallback to cookieTokenManager if professional not available
if (window.cookieTokenManager) {
try {
const token = await window.cookieTokenManager.getToken(
'WorkspaceManagement'
);
if (token) {
this.userToken = token;
// Get user info from cookie manager
const userInfo = window.cookieTokenManager.getUserInfo();
this.userId = userInfo.contactId || userInfo.userId || 'unknown';
console.log(
'[WorkspaceManagement] 🍪 Authentication from cookie token manager'
);
this.isAuthenticated = true;
return;
}
} catch (error) {
console.warn(
'[WorkspaceManagement] Cookie token manager failed:',
error
);
}
}
// Final fallback to global token functions
if (window.generateToken) {
try {
const token = await window.generateToken('WorkspaceManagement');
if (token) {
this.userToken = token;
this.userId = window.getUserInfo?.()?.contactId || 'unknown';
console.log(
'[WorkspaceManagement] 🔑 Authentication from global generateToken'
);
this.isAuthenticated = true;
return;
}
} catch (error) {
console.warn(
'[WorkspaceManagement] Global token generation failed:',
error
);
}
}
// Instead of throwing error, set to demo mode
console.log(
'[WorkspaceManagement] No authentication available, using demo mode'
);
this.userToken = null;
this.userId = null;
} catch (error) {
console.error('[WorkspaceManagement] Authentication failed:', error);
this.userToken = null;
this.userId = null;
}
}
/**
* Get authentication service from global scope or create simple fallback
*/
getAuthService() {
// Try to access global token auth service if available
if (typeof window !== 'undefined' && window.tokenAuthService) {
return window.tokenAuthService;
}
return this.createFallbackAuthService();
}
/**
* Get operators service from global scope or create simple fallback
*/
getOperatorsService() {
// Try to access global page operators service if available
if (typeof window !== 'undefined' && window.pageOperatorsService) {
return window.pageOperatorsService;
}
return this.createFallbackOperatorsService();
}
/**
* Create safe fallback auth service with enhanced authentication timing
*/
createFallbackAuthService() {
return {
isUserAuthenticated: () => {
try {
if (typeof window !== 'undefined') {
// Check multiple authentication sources for robust detection
return !!(
// Power Pages Simple Token Manager
(
(window.auth &&
typeof window.auth.getToken === 'function' &&
window.auth.getToken()) ||
// Direct PowerPages auth data
(window.PowerPagesAuth && window.PowerPagesAuth.token) ||
// Global variables set by liquid template
window.bsp_token ||
// Cached tokens
localStorage.getItem('bsp_token') ||
sessionStorage.getItem('bsp_token') ||
// Guest token also counts as "authenticated" for functionality
sessionStorage.getItem('bsp_guest_token')
)
);
}
return false;
} catch (error) {
console.warn('[Auth] Authentication check failed:', error);
return false;
}
},
// Enhanced method with retry logic for token generation timing
isUserAuthenticatedWithRetry: async (
maxWaitTime = 5000,
checkInterval = 250
) => {
const startTime = Date.now();
// Only log in console, not to user interface
console.log('[Auth] Checking for authentication token...');
while (Date.now() - startTime < maxWaitTime) {
try {
// Check multiple token sources for robust authentication
if (
typeof window !== 'undefined' &&
// Power Pages Simple Token Manager
((window.auth &&
typeof window.auth.getToken === 'function' &&
!!window.auth.getToken()) ||
// Direct PowerPages auth data
(window.PowerPagesAuth && window.PowerPagesAuth.token) ||
// Global variables set by liquid template
window.bsp_token ||
// localStorage cached token
localStorage.getItem('bsp_token') ||
// Session storage token
sessionStorage.getItem('bsp_token'))
) {
console.log('[Auth] Authentication token found');
return true;
}
} catch (error) {
console.warn('[Auth] Token check failed:', error);
}
// Shorter wait interval for faster response
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
// Log timeout only to console, generate fallback token for functionality
console.log(
'[Auth] Token check timeout - generating fallback token for public access'
);
// Generate a fallback guest token to ensure functionality
this.generateFallbackGuestToken();
return true; // Return true to allow page to function with guest access
},
// Generate fallback guest token for public functionality
generateFallbackGuestToken: () => {
try {
const guestToken =
'guest_' +
Date.now() +
'_' +
Math.random().toString(36).substr(2, 9);
if (typeof window !== 'undefined') {
// Store guest token for session
sessionStorage.setItem('bsp_guest_token', guestToken);
console.log('[Auth] Generated guest token for public access');
}
} catch (error) {
console.warn('[Auth] Failed to generate guest token:', error);
}
},
getToken: () => {
try {
if (typeof window !== 'undefined') {
// Try multiple token sources for robust authentication
// 1. Power Pages Simple Token Manager (primary)
if (window.auth && typeof window.auth.getToken === 'function') {
const token = window.auth.getToken();
if (token) return token;
}
// 2. Direct PowerPages auth data
if (window.PowerPagesAuth && window.PowerPagesAuth.token) {
return window.PowerPagesAuth.token;
}
// 3. Global variables set by liquid template
if (window.bsp_token) {
return window.bsp_token;
}
// 4. localStorage cached token
const cachedToken = localStorage.getItem('bsp_token');
if (cachedToken) return cachedToken;
// 5. Session storage token
const sessionToken = sessionStorage.getItem('bsp_token');
if (sessionToken) return sessionToken;
// 6. Guest token for public access
const guestToken = sessionStorage.getItem('bsp_guest_token');
if (guestToken) return guestToken;
}
} catch (error) {
console.warn('[Auth] Token access failed:', error);
}
return null;
},
getUserPermission: key => {
try {
if (typeof window === 'undefined') return null;
const mappings = {
contactId: window.contact_id || window.user_contactid,
userName: window.user_name,
name: window.user_name,
userEmail: window.user_email,
email: window.user_email,
workspaceId: window.workspace_id,
accountId: window.account_id,
subscriptionId: window.subscription_id,
};
return mappings[key] || null;
} catch (error) {
console.warn('User permission access failed:', error);
return null;
}
},
};
}
/**
* Create safe fallback operators service
*/
createFallbackOperatorsService() {
return {
callOperator: async () => {
console.warn(
'Operators service not available, returning empty response'
);
return { success: false, message: 'Service not available' };
},
};
}
/**
* Initialize the page - IMMEDIATE RENDERING
* Shows UI instantly without waiting for authentication
*/
async initialize(_data = {}) {
if (this.initialized) return;
try {
// IMMEDIATE: Show the UI without any loading delays
this.renderImmediateUI();
// IMMEDIATE: Setup basic interactions
this.setupInteractions();
this.initialized = true;
// DEFERRED: Initialize authentication features in background
this.initializeAuthenticationFeaturesInBackground();
} catch (error) {
console.error('Error initializing account management page:', error);
this.showError(error);
}
}
/**
* Render immediate UI structure - shows content instantly
*/
renderImmediateUI() {
const container = document.getElementById(this.options.containerId);
if (!container) return;
// Clear any existing loading indicators
container.innerHTML = '';
// Show immediate account management interface
container.innerHTML = `
<div style="background: #f8f9fa; color: #022d54; font-family: Arial, sans-serif;">
<div style="max-width: 1200px; margin: 0 auto; padding: 2rem 1rem;">
<!-- Header -->
<div style="margin-bottom: 2rem;">
<h1 style="color: #022d54; font-weight: 300; font-size: 28px; margin-bottom: 0.5rem;">Account Management</h1>
<p style="color: #6c757d; font-size: 14px; margin: 0;">Manage your account settings and preferences</p>
</div>
<!-- Main Content Area -->
<div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden;">
<div id="page-content-area" style="padding: 2rem;">
<!-- Clean loading state -->
<div style="text-align: center; padding: 2rem;">
<div style="display: inline-block; width: 20px; height: 20px; border: 2px solid #e0e0e0; border-top: 2px solid #022d54; border-radius: 50%; animation: spin 1s linear infinite;"></div>
<p style="color: #6c757d; margin-top: 1rem;">Loading content...</p>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Animation -->
<style>
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
`;
}
/**
* Setup interactions - immediate setup without waiting
*/
setupInteractions() {
// TODO: Add page-specific interactions here
console.log('Account Management interactions ready');
}
/**
* Initialize authentication features completely in background - NO UI BLOCKING
* Now includes retry logic to wait for token generation as requested
*/
async initializeAuthenticationFeaturesInBackground() {
// Use requestIdleCallback for true background processing
const initializeAuth = async () => {
try {
// Ensure services are ready
if (!this.authService) {
this.initializeAuth();
}
console.log(
'[Site] Checking authentication status (allowing time for token generation)...'
);
// Check authentication status with retry logic for token generation
if (
this.authService &&
typeof this.authService.isUserAuthenticatedWithRetry === 'function'
) {
// Use the enhanced method that waits for token generation
this.isAuthenticated =
await this.authService.isUserAuthenticatedWithRetry(15000, 1000);
} else {
// Fallback to immediate check
this.isAuthenticated = this.authService
? this.authService.isUserAuthenticated()
: false;
}
if (this.isAuthenticated) {
console.log('[Site] User authenticated for page: account-management');
// Load data in background
this.loadDataInBackground();
} else {
console.log(
'[Site] User not authenticated for page: account-management'
);
this.showUnauthenticatedView();
}
} catch (error) {
console.warn(
'Background authentication features initialization failed:',
error
);
// Continue silently - authentication is not critical for UI display
this.showUnauthenticatedView();
}
};
// Use requestIdleCallback for non-blocking background execution
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(initializeAuth, { timeout: 5000 });
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(initializeAuth, 50);
}
}
/**
* Load data in background with comprehensive Dataverse queries
*/
async loadDataInBackground() {
try {
console.log(
'Loading comprehensive account management data in background...'
);
// Load comprehensive data from APIM Dataverse with multiple entity queries
await this.loadAccountDataFromAPIM();
// Update UI after data loading
setTimeout(() => {
this.updateUIWithActualContent();
}, 500);
} catch (error) {
console.error('Background comprehensive data loading failed:', error);
// Show basic UI even if data loading fails
this.updateUIWithActualContent();
}
}
/**
* Update UI with actual content (replace skeleton loading)
* Now using the static template structure from template.html
*/
updateUIWithActualContent() {
// Get user's preferred language
const language = this.getCurrentLanguage();
const translations = this.getTranslations(language);
// Hide loading state and show main content
const loadingState = document.getElementById('loading-state');
const mainContainer = document.getElementById('mainContainer');
if (loadingState) {
loadingState.style.display = 'none';
}
if (mainContainer) {
mainContainer.style.display = 'block';
}
// Initialize account management functionality
this.initializeAccountManagementFeatures(translations);
}
/**
* Get current language (German or English)
*/
getCurrentLanguage() {
// Check URL parameter first
const urlParams = new URLSearchParams(window.location.search);
const langParam = urlParams.get('lang');
if (langParam && ['en', 'de'].includes(langParam)) {
return langParam;
}
// Check localStorage
const storedLang = localStorage.getItem('bsp_language');
if (storedLang && ['en', 'de'].includes(storedLang)) {
return storedLang;
}
// Check browser language
const browserLang = navigator.language.toLowerCase();
if (browserLang.startsWith('de')) {
return 'de';
}
// Default to English
return 'en';
}
/**
* Get translations for the specified language
*/
getTranslations(language) {
const translations = {
en: {
title: 'Account Management',
subtitle: 'Manage your account settings and workspace access',
totalWorkspaces: 'TOTAL WORKSPACES',
totalLicenses: 'TOTAL LICENSES',
monthlyUsage: 'MONTHLY USAGE',
userCount: 'TOTAL USERS',
activeWorkspaces: 'Active workspaces',
licenseAllocation: 'License allocation',
thisMonth: 'This month',
totalUsers: 'Total users',
allWorkspaces: 'My Workspaces',
searchWorkspaces: 'Search workspaces...',
workspaceName: 'WORKSPACE NAME',
role: 'ROLE',
lastAccess: 'LAST ACCESS',
status: 'STATUS',
actions: 'ACTIONS',
noWorkspacesFound: 'No workspaces found',
noWorkspacesMessage: "You don't have access to any workspaces yet",
createWorkspace: 'Request Access',
accountSettings: 'Account Settings',
profileInfo: 'Profile Information',
securitySettings: 'Security Settings',
export: 'Export',
view: 'View',
edit: 'Edit',
active: 'Active',
inactive: 'Inactive',
admin: 'Admin',
member: 'Member',
viewer: 'Viewer',
loadingAccount: 'Loading account information...',
},
de: {
title: 'Kontoverwaltung',
subtitle:
'Verwalten Sie Ihre Kontoeinstellungen und Arbeitsbereich-Zugriffe',
totalWorkspaces: 'GESAMTE ARBEITSBEREICHE',
totalLicenses: 'GESAMTE LIZENZEN',
monthlyUsage: 'MONATLICHE NUTZUNG',
userCount: 'GESAMTE BENUTZER',
activeWorkspaces: 'Aktive Arbeitsbereiche',
licenseAllocation: 'Lizenzzuteilung',
thisMonth: 'Diesen Monat',
totalUsers: 'Gesamte Benutzer',
allWorkspaces: 'Meine Arbeitsbereiche',
searchWorkspaces: 'Arbeitsbereiche suchen...',
workspaceName: 'ARBEITSBEREICH-NAME',
role: 'ROLLE',
lastAccess: 'LETZTER ZUGRIFF',
status: 'STATUS',
actions: 'AKTIONEN',
noWorkspacesFound: 'Keine Arbeitsbereiche gefunden',
noWorkspacesMessage:
'Sie haben noch keinen Zugriff auf Arbeitsbereiche',
createWorkspace: 'Zugriff anfordern',
accountSettings: 'Kontoeinstellungen',
profileInfo: 'Profilinformationen',
securitySettings: 'Sicherheitseinstellungen',
export: 'Exportieren',
view: 'Anzeigen',
edit: 'Bearbeiten',
active: 'Aktiv',
inactive: 'Inaktiv',
admin: 'Administrator',
member: 'Mitglied',
viewer: 'Betrachter',
loadingAccount: 'Kontoinformationen werden geladen...',
},
};
return translations[language] || translations.en;
}
/**
* Initialize account management features
*/
initializeAccountManagementFeatures(translations) {
this.translations = translations;
this.accountData = {
workspaces: [],
stats: {
totalWorkspaces: 0,
totalLicenses: 0,
totalUsers: 0,
monthlyUsage: '0',
},
};
// Initialize workspace management system
this.initializeWorkspaceManagement();
// Update UI with current translations
this.updateUITranslations();
// Load account data from APIM APIs
this.loadAccountDataFromAPIM();
}
/**
* Initialize workspace management system with comprehensive business logic
*/
initializeWorkspaceManagement() {
this.currentWorkspace = null;
this.workspaces = [];
this.isEditMode = false;
this.originalValues = {};
this.userToken = null;
this.userId = null;
// Initialize authentication for workspace management
this.initializeWorkspaceAuth();
}
/**
* Load workspaces with user impersonation and hierarchy support via APIM Dataverse
*/
async loadWorkspacesWithHierarchy() {
try {
console.log(
'[WorkspaceManagement] 📡 Loading workspaces with APIM user impersonation...'
);
// Ensure proper authentication for Dataverse operations
if (!this.userToken || !this.userId) {
await this.initializeWorkspaceAuth();
}
if (!this.userToken) {
throw new Error(
'User authentication required for Dataverse workspace operations'
);
}
const response = await fetch(
`${this.apiBaseUrl}/administrative/workspace/list`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.userToken}`,
'X-User-Impersonation': this.userId,
'X-API-Version': '2024-01',
},
body: JSON.stringify({
user_id: this.userId,
user_token: this.userToken,
include_hierarchy: true,
include_aggregates: true,
include_related_entities: true,
impersonation_context: {
source: 'account_management',
permissions: ['workspace.read', 'workspace.manage'],
},
}),
}
);
if (!response.ok) {
throw new Error(
`APIM Dataverse API request failed: ${response.status} ${response.statusText}`
);
}
const data = await response.json();
if (!data.success) {
throw new Error(
data.message || 'Failed to load workspaces from Dataverse'
);
}
this.workspaces = data.workspaces || [];
console.log(
'[WorkspaceManagement] [SUCCESS] Loaded',
this.workspaces.length,
'workspaces from APIM Dataverse'
);
// Process workspace hierarchy and related entities
this.processWorkspaceHierarchy(data);
// Update UI with real data from Dataverse
this.updateWorkspaceTableWithHierarchy();
this.updateStatsFromWorkspaces();
} catch (error) {
console.error(
'[WorkspaceManagement] Failed to load workspaces from APIM:',
error
);
this.showError(
'Failed to load workspace data from Dataverse. Please try again.'
);
// Show demo data for development
this.loadDemoWorkspaceData();
}
}
/**
* Process workspace hierarchy and related entities from Dataverse
*/
processWorkspaceHierarchy(data) {
// Process parent-child relationships
this.workspaces.forEach(workspace => {
if (workspace.parentWorkspaceId) {
const parent = this.workspaces.find(
ws => ws.id === workspace.parentWorkspaceId
);
workspace.parentWorkspaceName = parent ? parent.name : 'Unknown Parent';
workspace.isRootLevel = false;
} else {
workspace.parentWorkspaceName = null;
workspace.isRootLevel = true;
}
// Process related entities from Dataverse
workspace.usersData =
data.users?.filter(u => u.workspaceId === workspace.id) || [];
workspace.licensesData =
data.licenses?.filter(l => l.workspaceId === workspace.id) || [];
workspace.costPoolsData =
data.costPools?.filter(cp => cp.workspaceId === workspace.id) || [];
// Update counts from actual data
workspace.users = workspace.usersData.length;
workspace.licenses = workspace.licensesData.length;
workspace.costPools = workspace.costPoolsData.map(cp => cp.name);
});
}
/**
* Load demo workspace data with proper hierarchy for development
* Follows the structured format requirements from the developer guide
*/
loadDemoWorkspaceDataWithHierarchy() {
console.log(
'[WorkspaceManagement] 📋 Loading demo data with structured hierarchy for development'
);
this.workspaces = [
{
id: 'ws-1',
name: 'Global Organization',
parentWorkspaceId: null,
parentWorkspaceName: null,
users: 1247,
licenses: 486,
costPools: ['Enterprise', 'Operations'],
status: 'active',
isRootLevel: true,
indentationLevel: 0,
hierarchyIndicator: '',
monthlyUsage: 62350, // 1247 users * 50 operations
usersData: [
{
id: 'u1',
name: 'John Doe',
email: 'john@company.com',
workspaceId: 'ws-1',
},
{
id: 'u2',
name: 'Jane Smith',
email: 'jane@company.com',
workspaceId: 'ws-1',
},
],
licensesData: [
{
id: 'l1',
type: 'Professional',
status: 'active',
workspaceId: 'ws-1',
},
{
id: 'l2',
type: 'Enterprise',
status: 'active',
workspaceId: 'ws-1',
},
],
costPoolsData: [
{
id: 'cp1',
name: 'Enterprise',
budget: '$50,000',
workspaceId: 'ws-1',
},
{
id: 'cp2',
name: 'Operations',
budget: '$25,000',
workspaceId: 'ws-1',
},
],
},
{
id: 'ws-2',
name: 'Enterprise Division',
parentWorkspaceId: 'ws-1',
parentWorkspaceName: 'Global Organization',
users: 342,
licenses: 125,
costPools: ['Enterprise'],
status: 'active',
isRootLevel: false,
indentationLevel: 1,
hierarchyIndicator: '└─ ',
monthlyUsage: 17100, // 342 users * 50 operations
usersData: [
{
id: 'u3',
name: 'Mike Wilson',
email: 'mike@enterprise.com',
workspaceId: 'ws-2',
},
],
licensesData: [
{
id: 'l3',
type: 'Professional',
status: 'active',
workspaceId: 'ws-2',
},
],
costPoolsData: [
{
id: 'cp3',
name: 'Enterprise Dev',
budget: '$15,000',
workspaceId: 'ws-2',
},
],
},
{
id: 'ws-3',
name: 'Product Development',
parentWorkspaceId: 'ws-1',
parentWorkspaceName: 'Global Organization',
users: 185,
licenses: 87,
costPools: ['R&D', 'Engineering'],
status: 'active',
isRootLevel: false,
indentationLevel: 1,
hierarchyIndicator: '└─ ',
monthlyUsage: 9250, // 185 users * 50 operations
usersData: [
{
id: 'u4',
name: 'Sarah Connor',
email: 'sarah@product.com',
workspaceId: 'ws-3',
},
],
licensesData: [
{
id: 'l4',
type: 'Developer',
status: 'active',
workspaceId: 'ws-3',
},
],
costPoolsData: [
{ id: 'cp4', name: 'R&D', budget: '$30,000', workspaceId: 'ws-3' },
],
},
{
id: 'ws-4',
name: 'Engineering Team',
parentWorkspaceId: 'ws-3',
parentWorkspaceName: 'Product Development',
users: 45,
licenses: 23,
costPools: ['Engineering'],
status: 'active',
isRootLevel: false,
indentationLevel: 2,
hierarchyIndicator: ' └─ ',
monthlyUsage: 2250, // 45 users * 50 operations
usersData: [
{
id: 'u5',
name: 'Alex Tech',
email: 'alex@engineering.com',
workspaceId: 'ws-4',
},
],
licensesData: [
{
id: 'l5',
type: 'Developer',
status: 'active',
workspaceId: 'ws-4',
},
],
costPoolsData: [
{
id: 'cp5',
name: 'Engineering Core',
budget: '$8,000',
workspaceId: 'ws-4',
},
],
},
];
// Sort for hierarchical display
this.workspaces = this.sortWorkspacesForHierarchicalDisplay(
this.workspaces
);
this.updateWorkspaceTableWithStructuredHierarchy();
this.updateStatsFromComprehensiveData();
}
/**
* Update workspace table with structured hierarchy and proper indentation
* Implements the styling requirements from the root example
*/
updateWorkspaceTableWithStructuredHierarchy() {
const tbody = document.getElementById('workspaceTableBody');
const emptyState = document.getElementById('emptyState');
if (!tbody) return;
if (this.workspaces.length === 0) {
tbody.innerHTML = '';
if (emptyState) emptyState.style.display = 'block';
return;
}
if (emptyState) emptyState.style.display = 'none';
tbody.innerHTML = this.workspaces
.map(workspace => {
const isHierarchical = workspace.parentWorkspaceId !== null;
const hierarchyClass = isHierarchical ? 'workspace-hierarchy' : '';
const indentationStyle =
workspace.indentationLevel > 0
? `padding-left: ${workspace.indentationLevel * 20}px;`
: '';
return `
<tr onclick="window.accountManagementPage.openWorkspaceDetails('${workspace.id}')" style="cursor: pointer;">
<td>
<div class="workspace-name ${hierarchyClass}" style="${indentationStyle}">
${workspace.hierarchyIndicator}${workspace.name}
</div>
${
workspace.parentWorkspaceName
? `<div class="parent-workspace" style="${indentationStyle}">↳ ${workspace.parentWorkspaceName}</div>`
: ''
}
</td>
<td>${workspace.parentWorkspaceName || '—'}</td>
<td class="center"><span class="stat-number">${workspace.users}</span></td>
<td class="center"><span class="stat-number">${workspace.licenses}</span></td>
<td>
${
workspace.costPools
?.map(cp => `<span class="role-badge member">${cp}</span>`)
.join(' ') || '—'
}
</td>
<td class="center">
<span class="status-badge status-${workspace.status}">
${workspace.status}
</span>
</td>
<td class="center">
<div class="actions-cell">
<button class="action-btn" title="View Details" onclick="event.stopPropagation(); window.accountManagementPage.openWorkspaceDetails('${workspace.id}')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
</button>
<button class="action-btn" title="Edit Workspace" onclick="event.stopPropagation(); window.accountManagementPage.openWorkspaceDetails('${workspace.id}', true)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
</td>
</tr>
`;
})
.join('');
}
/**
* Legacy method name for backward compatibility
*/
updateWorkspaceTableWithHierarchy() {
return this.updateWorkspaceTableWithStructuredHierarchy();
}
/**
* Update stats from comprehensive Dataverse data
*/
updateStatsFromComprehensiveData() {
const totalWorkspaces = this.workspaces.length;
const totalUsers = this.workspaces.reduce((sum, ws) => sum + ws.users, 0);
const totalLicenses = this.workspaces.reduce(
(sum, ws) => sum + ws.licenses,
0
);
const allCostPools = new Set();
this.workspaces.forEach(ws => {
ws.costPools?.forEach(cp => allCostPools.add(cp));
});
// Calculate monthly usage from actual data - sum from workspace usage metrics
let monthlyUsage = 0;
this.workspaces.forEach(ws => {
// If workspace has usage data, add it
if (ws.monthlyUsage && typeof ws.monthlyUsage === 'number') {
monthlyUsage += ws.monthlyUsage;
} else if (ws.usersData && ws.usersData.length > 0) {
// Estimate based on user activity if no explicit usage data
monthlyUsage += ws.usersData.length * 50; // Estimate 50 operations per user per month
}
});
// Format monthly usage for display
let monthlyUsageDisplay = '0';
if (monthlyUsage >= 1000) {
monthlyUsageDisplay = (monthlyUsage / 1000).toFixed(1) + 'k';
} else {
monthlyUsageDisplay = monthlyUsage.toString();
}
// Update account stats with comprehensive data from actual operations
this.accountData = this.accountData || {};
this.accountData.stats = {
totalWorkspaces,
totalUsers,
totalLicenses,
monthlyUsage: monthlyUsageDisplay,
costPools: allCostPools.size,
};
// Update UI elements
this.updateStatsDisplay();
}
/**
* Legacy method name for backward compatibility
*/
updateStatsFromWorkspaces() {
return this.updateStatsFromComprehensiveData();
}
/**
* Open workspace details with comprehensive APIM Dataverse management
*/
async openWorkspaceDetails(workspaceId, editMode = false) {
console.log(
'[WorkspaceManagement] Opening comprehensive workspace details via APIM:',
workspaceId
);
try {
// Find workspace in loaded data
const workspace = this.workspaces.find(ws => ws.id === workspaceId);
if (!workspace) {
throw new Error('Workspace not found');
}
this.currentWorkspace = workspace;
this.isEditMode = editMode;
// Load detailed workspace data with all related entities from Dataverse
await this.loadComprehensiveWorkspaceDetailsFromDataverse(workspaceId);
// Show comprehensive workspace details UI
this.showComprehensiveWorkspaceDetailsModal(workspace);
} catch (error) {
console.error(
'[WorkspaceManagement] Failed to open comprehensive workspace details:',
error
);
this.showError(
'Failed to load comprehensive workspace details from Dataverse'
);
}
}
/**
* Load comprehensive workspace details from APIM Dataverse with all entity queries
*/
async loadComprehensiveWorkspaceDetailsFromDataverse(workspaceId) {
try {
console.log(
'[WorkspaceManagement] Loading comprehensive workspace details from Dataverse:',
workspaceId
);
if (!this.userToken) {
throw new Error(
'Authentication required for comprehensive Dataverse operations'
);
}
// Comprehensive API call for workspace details with all related entities
const response = await fetch(
`${this.apiBaseUrl}/administrative/workspace/details`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.userToken}`,
'X-User-Impersonation': this.userId,
'X-Include-Entities':
'accounts,contacts,subscriptions,costpools,permissions',
},
body: JSON.stringify({
workspace_id: workspaceId,
user_id: this.userId,
user_token: this.userToken,
// Comprehensive entity queries
include_users: true,
include_licenses: true,
include_cost_pools: true,
include_permissions: true,
include_parent_relationships: true,
include_child_relationships: true,
include_aggregated_stats: true,
}),
}
);
if (!response.ok) {
throw new Error(
`Comprehensive Dataverse API request failed: ${response.status} ${response.statusText}`
);
}
const data = await response.json();
if (!data.success) {
throw new Error(
data.message ||
'Failed to load comprehensive workspace details from Dataverse'
);
}
// Update current workspace with comprehensive data from all Dataverse entities
Object.assign(this.currentWorkspace, data.workspace);
this.currentWorkspace.usersData = data.users || [];
this.currentWorkspace.licensesData = data.licenses || [];
this.currentWorkspace.costPoolsData = data.costPools || [];
this.currentWorkspace.permissions = data.permissions || [];
this.currentWorkspace.parentRelationships =
data.parentRelationships || [];
this.currentWorkspace.childRelationships = data.childRelationships || [];
console.log(
'[WorkspaceManagement] [SUCCESS] Loaded comprehensive workspace details from Dataverse with all entities'
);
} catch (error) {
console.error(
'[WorkspaceManagement] Failed to load comprehensive workspace details from Dataverse:',
error
);
throw error;
}
}
/**
* Show comprehensive workspace details modal with all entity information
*/
showComprehensiveWorkspaceDetailsModal(workspace) {
const hierarchyInfo = workspace.parentWorkspaceName
? `Parent: ${workspace.parentWorkspaceName} (Level ${workspace.indentationLevel})`
: 'Root Level Workspace';
const details = `
COMPREHENSIVE WORKSPACE DETAILS FROM DATAVERSE
=============================================
Workspace: ${workspace.name}
${hierarchyInfo}
Hierarchy Indicator: ${workspace.hierarchyIndicator}
Status: ${workspace.status}
ENTITY STATISTICS:
- Users (Contacts): ${workspace.users}
- Licenses (Subscriptions): ${workspace.licenses}
- Cost Pools: ${workspace.costPools?.join(', ') || 'None'}
- Permissions: ${workspace.permissions?.length || 0}
RELATED ENTITY DETAILS:
- Users Data: ${workspace.usersData?.length || 0} contact records
- License Data: ${workspace.licensesData?.length || 0} subscription records
- Cost Pool Data: ${workspace.costPoolsData?.length || 0} cost pool records
- Parent Relationships: ${workspace.parentRelationships?.length || 0}
- Child Relationships: ${workspace.childRelationships?.length || 0}
DATA SOURCE: APIM Dataverse API with User Impersonation
Entity Queries: accounts, contacts, subscriptions, costpools, permissions
Structured Format: Hierarchical with parent relationship indentation
`;
alert(details);
// TODO: Replace with proper comprehensive modal UI implementation
console.log(
'[WorkspaceManagement] Comprehensive workspace details loaded from all Dataverse entities:',
workspace
);
}
/**
* Export comprehensive workspace data with structured hierarchy format
*/
exportComprehensiveWorkspaceData() {
console.log(
'[WorkspaceManagement] Exporting comprehensive workspace data from APIM Dataverse...'
);
const exportData = {
workspaces: this.workspaces,
exportDate: new Date().toISOString(),
userId: this.userId,
source: 'APIM_Dataverse_Comprehensive',
apiBaseUrl: this.apiBaseUrl,
version: '2024.01',
dataStructure: 'hierarchical_with_indentation',
entityQueries: [
'accounts',
'contacts',
'subscriptions',
'costpools',
'permissions',
],
metadata: {
totalWorkspaces: this.workspaces.length,
totalUsers: this.workspaces.reduce(
(sum, ws) => sum + (ws.users || 0),
0
),
totalLicenses: this.workspaces.reduce(
(sum, ws) => sum + (ws.licenses || 0),
0
),
hierarchicalWorkspaces: this.workspaces.filter(ws => !ws.isRootLevel)
.length,
rootWorkspaces: this.workspaces.filter(ws => ws.isRootLevel).length,
maxIndentationLevel: Math.max(
...this.workspaces.map(ws => ws.indentationLevel || 0)
),
parentChildRelationships: this.workspaces.filter(
ws => ws.parentWorkspaceId
).length,
},
structuredHierarchy: this.workspaces.map(ws => ({
id: ws.id,
name: ws.name,
level: ws.indentationLevel,
indicator: ws.hierarchyIndicator,
parent: ws.parentWorkspaceName,
isRoot: ws.isRootLevel,
})),
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `comprehensive-workspace-dataverse-export-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
console.log(
'[WorkspaceManagement] Comprehensive workspace data exported successfully from APIM Dataverse'
);
}
/**
* Update UI translations for all elements
*/
updateUITranslations() {
// Update text content based on current translations
const updateElement = (id, key) => {
const element = document.getElementById(id);
if (element && this.translations[key]) {
element.textContent = this.translations[key];
}
};
// Header elements
updateElement('page-title', 'title');
updateElement('page-subtitle', 'subtitle');
updateElement('export-btn-text', 'export');
// Stats labels
updateElement('total-workspaces-label', 'totalWorkspaces');
updateElement('total-licenses-label', 'totalLicenses');
updateElement('monthly-usage-label', 'monthlyUsage');
updateElement('user-count-label', 'userCount');
// Stats subtexts
updateElement('active-workspaces-text', 'activeWorkspaces');
updateElement('license-allocation-text', 'licenseAllocation');
updateElement('this-month-text', 'thisMonth');
updateElement('user-count-text', 'totalUsers');
// Table elements
updateElement('my-workspaces-title', 'allWorkspaces');
updateElement('workspace-name-header', 'workspaceName');
updateElement('role-header', 'role');
updateElement('last-access-header', 'lastAccess');
updateElement('status-header', 'status');
updateElement('actions-header', 'actions');
// Empty state
updateElement('no-workspaces-title', 'noWorkspacesFound');
updateElement('no-workspaces-text', 'noWorkspacesMessage');
updateElement('loading-text', 'loadingAccount');
// Update search placeholder
const searchInput = document.getElementById('searchInput');
if (searchInput && this.translations.searchWorkspaces) {
searchInput.placeholder = this.translations.searchWorkspaces;
}
}
/**
* Load account data from APIM with comprehensive Dataverse queries
* Implements multiple entity queries as required by developer guide
*/
async loadAccountDataFromAPIM() {
try {
console.log(
'[AccountManagement] Loading account data from APIM Dataverse...'
);
// Ensure authentication is available - fail if not authenticated
if (!this.authService?.isUserAuthenticated()) {
throw new Error(
'Authentication required for account management operations'
);
}
// Load comprehensive workspace data using APIM operators
await this.loadWorkspacesWithAPIMOperators();
} catch (error) {
console.error('[AccountManagement] Failed to load account data:', error);
throw error; // Re-throw error instead of falling back to sample data
}
}
/**
* Load workspaces with comprehensive Dataverse queries for multiple entities
* Implements structured format with parent account relationship indentation
*/
/**
* DEPRECATED: Load workspaces with comprehensive Dataverse queries for multiple entities
* Use loadWorkspacesWithAPIMOperators() instead
*/
async loadWorkspacesWithComprehensiveDataverseQueries() {
console.warn(
'[AccountManagement] loadWorkspacesWithComprehensiveDataverseQueries is deprecated. Use loadWorkspacesWithAPIMOperators() instead.'
);
throw new Error(
'Direct Dataverse queries are deprecated. Use APIM operators instead.'
);
}
/**
* Process comprehensive Dataverse response with multiple entities
* Implements structured format with parent account relationship indentation
*/
processComprehensiveDataverseResponse(data) {
console.log(
'[AccountManagement] Processing comprehensive Dataverse response with multiple entities...'
);
// Extract workspaces from account entities
this.workspaces = data.workspaces || [];
// Process parent-child relationships with proper indentation structure
this.workspaces.forEach(workspace => {
if (workspace.parentWorkspaceId) {
const parent = this.workspaces.find(
ws => ws.id === workspace.parentWorkspaceId
);
workspace.parentWorkspaceName = parent ? parent.name : 'Unknown Parent';
workspace.isRootLevel = false;
// Add indentation level for hierarchical display
workspace.indentationLevel = this.calculateIndentationLevel(workspace);
workspace.hierarchyIndicator = this.getHierarchyIndicator(
workspace.indentationLevel
);
} else {
workspace.parentWorkspaceName = null;
workspace.isRootLevel = true;
workspace.indentationLevel = 0;
workspace.hierarchyIndicator = '';
}
// Process related entities from comprehensive Dataverse queries
workspace.usersData =
data.users?.filter(u => u.workspaceId === workspace.id) || [];
workspace.licensesData =
data.licenses?.filter(l => l.workspaceId === workspace.id) || [];
workspace.costPoolsData =
data.costPools?.filter(cp => cp.workspaceId === workspace.id) || [];
workspace.permissions =
data.permissions?.filter(p => p.workspaceId === workspace.id) || [];
// Update counts from actual Dataverse entity data
workspace.users = workspace.usersData.length;
workspace.licenses = workspace.licensesData.length;
workspace.costPools = workspace.costPoolsData.map(cp => cp.name);
});
// Sort workspaces to show hierarchy properly with parent relationships first
this.works