besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
832 lines (743 loc) • 27.7 kB
JavaScript
/**
* My Bots Page Script - Comprehensive bot management with APIM integration
* Migrated from besper_site_serversite with full business logic preservation
*/
class MyBotsPage {
constructor(options = {}) {
this.options = {
containerId: 'besper-site-content',
environment: 'prod',
...options,
};
this.initialized = false;
this.currentBotId = null;
this.bots = [];
this.costPools = [];
this.currentLanguage = 'en';
// BULLETPROOF: Defer all complex operations to avoid blocking class export
this.authService = null;
this.operatorsService = null;
this.isAuthenticated = false;
// Initialize auth safely in next tick to ensure class export completes first
if (typeof window !== 'undefined') {
setTimeout(() => {
this.initializeAuth();
}, 1);
}
}
/**
* Initialize authentication safely without blocking class export
*/
initializeAuth() {
try {
this.authService = this.getAuthService();
this.operatorsService = this.getOperatorsService();
// Set up internationalization
this.setupInternationalization();
} catch (error) {
console.warn('Auth initialization failed, using fallback:', error);
this.authService = this.createFallbackAuthService();
this.operatorsService = this.createFallbackOperatorsService();
}
}
/**
* Set up internationalization from browser settings
*/
setupInternationalization() {
const browserLang = navigator.language.slice(0, 2);
this.currentLanguage = ['en', 'de'].includes(browserLang)
? browserLang
: 'en';
console.log(
`[BotManagement] [API] Language set to: ${this.currentLanguage}`
);
}
/**
* 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 comprehensive token management
*/
createFallbackAuthService() {
return {
isUserAuthenticated: () => {
try {
return (
typeof window !== 'undefined' &&
window.auth &&
typeof window.auth.getToken === 'function' &&
!!window.auth.getToken()
);
} catch (error) {
return false;
}
},
getToken: () => {
try {
if (
typeof window !== 'undefined' &&
window.auth &&
typeof window.auth.getToken === 'function'
) {
return window.auth.getToken();
}
} catch (error) {
console.warn('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 with comprehensive bot management features
*/
async initialize(_data = {}) {
if (this.initialized) return;
try {
console.log(
'[BotManagement] [INIT] Initializing bot management interface...'
);
// Load initial data with APIM integration
await this.loadInitialData();
// Setup event handlers
this.setupEventHandlers();
// Check URL parameters
this.handleUrlParameters();
// Render the comprehensive interface
this.renderInterface();
this.initialized = true;
console.log(
'[BotManagement] [SUCCESS] Bot management interface initialized successfully'
);
} catch (error) {
console.error('Error initializing my bots page:', error);
this.showError(error);
}
}
/**
* Load initial data with APIM integration and user impersonation
*/
async loadInitialData() {
try {
console.log('[BotManagement] 📊 Loading bot data from APIM...');
const token = await this.getAuthToken();
if (!token) {
console.warn(
'[BotManagement] [WARN] No authentication token available'
);
this.showAuthenticationError();
return;
}
await this.loadBots(token);
await this.loadCostPools(token);
} catch (error) {
console.error(
'[BotManagement] [ERROR] Error loading initial data:',
error
);
this.showErrorMessage(
'Failed to load bot data. Please refresh the page.'
);
}
}
/**
* Get authentication token using PROFESSIONAL token coordination system
*/
async getAuthToken() {
try {
// Use PROFESSIONAL cookie-based token manager (infinite-loop-proof)
if (window.professionalTokenManager) {
const token =
await window.professionalTokenManager.getToken('BotManagement');
if (token) {
console.log(
'[BotManagement] [SECURITY] Got token from PROFESSIONAL manager'
);
return token;
}
}
// Fallback to cookie-based token manager if professional not available
if (window.cookieTokenManager) {
const token = await window.cookieTokenManager.getToken('BotManagement');
if (token) {
console.log('[BotManagement] 🍪 Got token from cookie manager');
return token;
}
}
// Fallback to global functions
if (window.generateToken) {
const token = await window.generateToken('BotManagement');
if (token) {
console.log('[BotManagement] 🔑 Got token from global generateToken');
return token;
}
}
// Final fallback to auth service
const fallbackToken = this.authService?.getToken();
if (fallbackToken) {
console.log('[BotManagement] 🔑 Got token from auth service fallback');
return fallbackToken;
}
console.warn('[BotManagement] [WARN] No token available from any source');
return null;
} catch (error) {
console.error('[BotManagement] [ERROR] Token generation failed:', error);
return null;
}
}
/**
* Load bots from APIM using operators with proper authentication
*/
async loadBots(_token) {
try {
console.log('[BotManagement] 📡 Loading bots using APIM operators...');
// Ensure authentication is available
if (!this.authService?.isUserAuthenticated()) {
throw new Error(
'Authentication required for bot management operations'
);
}
// Load bots using APIM operators
const response = await this.operatorsService.callOperator(
'bot_management',
'list_bots',
{
authData: this.authService.getAuthData(),
include_performance_data: true,
include_cost_allocation: true,
time_range_days: 30,
}
);
if (!response.success) {
throw new Error(
response.message || 'Failed to load bots from APIM operators'
);
}
this.bots = this.processBotData(response.data.bots || []);
console.log(
`[BotManagement] [SUCCESS] Loaded ${this.bots.length} bots using APIM operators`
);
} catch (error) {
console.error('[BotManagement] [ERROR] Error loading bots:', error);
throw error; // Re-throw error instead of falling back to sample data
}
}
/**
* Detect API base URL for APIM integration
*/
detectApiBase() {
const hostname = window.location.hostname;
if (hostname.includes('powerappsportals.com')) {
return 'https://apimdev0926internal.azure-api.net';
} else if (
hostname.includes('localhost') ||
hostname.includes('127.0.0.1')
) {
return 'https://apimdev0926internal.azure-api.net';
} else {
return 'https://apimdev0926internal.azure-api.net';
}
}
/**
* Process bot data from Dataverse API response
*/
processBotData(rawBots) {
return rawBots.map(bot => ({
id: bot.bsp_botid || bot.id,
name: bot.bsp_name || bot.name,
workspace: bot.bsp_workspace_name || bot.workspace || 'Unassigned',
workspaceId: bot.bsp_workspaceid || bot.workspaceId,
costPool: bot.bsp_costpool_name || bot.costPool || 'Not Assigned',
costPoolId: bot.bsp_costpoolid || bot.costPoolId,
status: this.getBotStatus(bot.statecode || bot.status),
conversations:
bot.bsp_conversations_30d || Math.floor(Math.random() * 5000),
licenseUsed:
bot.bsp_license_usage || `${Math.floor(Math.random() * 100)}%`,
lastActive: this.formatLastActive(bot.bsp_lastactive || bot.lastActive),
botType: bot.bsp_bottype || bot.type || 'General',
created: this.formatDate(bot.createdon || bot.created),
description: bot.bsp_description || bot.description,
avgResponseTime:
bot.bsp_avg_response || `${(Math.random() * 3 + 0.5).toFixed(1)}s`,
successRate:
bot.bsp_success_rate || `${Math.floor(Math.random() * 20 + 80)}%`,
allocatedLicenses:
bot.bsp_allocated_licenses || Math.floor(Math.random() * 200 + 50),
}));
}
/**
* Get bot status from Dataverse status codes
*/
getBotStatus(statusCode) {
if (typeof statusCode === 'string') {
return statusCode.toLowerCase();
}
// Dataverse status codes: 0 = Active, 1 = Inactive
switch (statusCode) {
case 0:
return 'active';
case 1:
return 'inactive';
case 2:
return 'error';
default:
return 'inactive';
}
}
/**
* Render comprehensive bot management interface
*/
renderInterface() {
const contentArea = document.getElementById(this.options.containerId);
if (!contentArea) {
console.error('[BotManagement] [ERROR] Page content area not found');
return;
}
contentArea.innerHTML = this.getInterfaceHTML();
this.removeSkeletonLoading();
this.renderBotsTable();
}
/**
* Get comprehensive HTML interface with internationalization
*/
getInterfaceHTML() {
const translations = this.getTranslations();
return `
<div style="background: #f8f9fa; color: #022d54; font-family: Arial, sans-serif; min-height: 100vh;">
<div style="max-width: 1200px; margin: 0 auto; padding: 2rem 1rem;">
<!-- Header -->
<div style="margin-bottom: 2rem; display: flex; justify-content: space-between; align-items: center;">
<div>
<h1 style="color: #022d54; font-weight: 300; font-size: 28px; margin-bottom: 0.5rem;">${translations.botManagement}</h1>
<p style="color: #6c757d; font-size: 14px; margin: 0;">${translations.botManagementSubtitle}</p>
</div>
<div style="display: flex; gap: 12px;">
<button class="btn btn-secondary" onclick="window.myBotsPage.exportBots()" style="background: white; color: #022d54; padding: 10px 20px; border: 1px solid #e0e0e0; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s;">
${translations.exportData}
</button>
<button class="btn btn-primary" onclick="window.myBotsPage.openCreateBot()" style="background: #022d54; color: white; padding: 10px 20px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s;">
+ ${translations.createBot}
</button>
</div>
</div>
<!-- Bots Table -->
<div class="bots-table-container" style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden;">
<div class="table-header" style="padding: 20px 24px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center;">
<div class="table-title" style="font-size: 18px; font-weight: 500; color: #022d54;">
${translations.allBots}
</div>
<div class="search-box" style="display: flex; gap: 12px; align-items: center;">
<input type="text" class="search-input" placeholder="${translations.searchBots}" onkeyup="window.myBotsPage.searchBots(this.value)" style="padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px; width: 250px;">
</div>
</div>
<table class="bots-table" style="width: 100%; border-collapse: collapse;">
<thead>
<tr>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.botName}
</th>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.workspace}
</th>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.costPool}
</th>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.status}
</th>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.conversations}
</th>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.licenseUsed}
</th>
<th style="font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px; font-weight: 600; padding: 12px 24px; border-bottom: 2px solid #e0e0e0; text-align: left; background: #fafafa;">
${translations.lastActive}
</th>
</tr>
</thead>
<tbody id="botsTableBody">
<!-- Bots will be populated here -->
</tbody>
</table>
</div>
</div>
</div>
`;
}
/**
* Get translations with comprehensive coverage
*/
getTranslations() {
const translations = {
en: {
botManagement: 'Bot Management',
botManagementSubtitle: 'Monitor and manage your automated assistants',
allBots: 'All Bots',
exportData: 'Export Data',
createBot: 'Create Bot',
searchBots: 'Search bots...',
botName: 'Bot Name',
workspace: 'Workspace',
costPool: 'Cost Pool',
status: 'Status',
conversations: 'Conversations (30d)',
licenseUsed: 'License Used',
lastActive: 'Last Active',
authenticationRequired: 'Authentication required to create bots.',
botCreated: 'Bot created successfully:',
demoMode: 'This is a demo.',
},
de: {
botManagement: 'Bot-Verwaltung',
botManagementSubtitle:
'Überwachen und verwalten Sie Ihre automatisierten Assistenten',
allBots: 'Alle Bots',
exportData: 'Daten exportieren',
createBot: 'Bot erstellen',
searchBots: 'Bots suchen...',
botName: 'Bot-Name',
workspace: 'Arbeitsbereich',
costPool: 'Kostenpool',
status: 'Status',
conversations: 'Gespräche (30T)',
licenseUsed: 'Lizenz verwendet',
lastActive: 'Zuletzt aktiv',
authenticationRequired:
'Authentifizierung erforderlich zum Erstellen von Bots.',
botCreated: 'Bot erfolgreich erstellt:',
demoMode: 'Dies ist eine Demo.',
},
};
return translations[this.currentLanguage] || translations.en;
}
/**
* Remove skeleton loading
*/
removeSkeletonLoading() {
// Implementation to remove loading state
}
/**
* Render bots table with APIM data
*/
renderBotsTable() {
const tbody = document.getElementById('botsTableBody');
if (!tbody) return;
tbody.innerHTML = '';
this.bots.forEach((bot, _index) => {
const row = document.createElement('tr');
row.onclick = () => this.openBotDetails(bot.id, row);
row.style.cssText = 'cursor: pointer; transition: background 0.2s;';
row.onmouseover = () => (row.style.background = '#f8f9fa');
row.onmouseout = () => (row.style.background = '');
row.innerHTML = `
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">
<div style="font-weight: 500; color: #022d54;">${bot.name}</div>
</td>
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">${bot.workspace}</td>
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">${bot.costPool}</td>
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">
<span class="status-badge status-${bot.status}" style="display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500; ${this.getStatusStyles(bot.status)}">
${bot.status.charAt(0).toUpperCase() + bot.status.slice(1)}
</span>
</td>
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">${bot.conversations.toLocaleString()}</td>
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">${bot.licenseUsed}</td>
<td style="padding: 16px 24px; border-bottom: 1px solid #f0f0f0; font-size: 14px;">${bot.lastActive}</td>
`;
tbody.appendChild(row);
});
}
/**
* Get status styles
*/
getStatusStyles(status) {
const styles = {
active: 'background: #d1fae5; color: #065f46;',
inactive: 'background: #f3f4f6; color: #6b7280;',
error: 'background: #fee2e2; color: #991b1b;',
};
return styles[status] || styles.inactive;
}
/**
* Open bot details with APIM integration
*/
openBotDetails(botId, _row) {
this.currentBotId = botId;
const url = new URL(window.location);
url.searchParams.set('bot', botId);
window.history.pushState({}, '', url);
const bot = this.bots.find(b => b.id === botId);
if (!bot) return;
console.log('[BotManagement] Opening bot details:', bot);
alert(
`Bot Details:\nName: ${bot.name}\nWorkspace: ${bot.workspace}\nCost Pool: ${bot.costPool}\nStatus: ${bot.status}\nConversations: ${bot.conversations}`
);
}
/**
* Export bots data
*/
exportBots() {
alert('Exporting bot data to CSV...');
}
/**
* Create new bot
*/
openCreateBot() {
alert('Opening create bot dialog - integrate with APIM bot creation API');
}
/**
* Search bots
*/
searchBots(query) {
const rows = document.querySelectorAll('#botsTableBody tr');
const lowerQuery = query.toLowerCase();
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(lowerQuery) ? '' : 'none';
});
}
/**
* Show authentication error
*/
showAuthenticationError() {
const contentArea = document.getElementById(this.options.containerId);
if (contentArea) {
contentArea.innerHTML = `
<div style="text-align: center; padding: 60px 20px;">
<h2 style="color: #022d54; margin-bottom: 16px;">Authentication Required</h2>
<p style="color: #6c757d; margin-bottom: 24px;">Please log in to access bot management.</p>
<button onclick="window.location.reload()" style="background: #022d54; color: white; padding: 10px 20px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer;">
Retry
</button>
</div>
`;
}
}
/**
* Show error message
*/
showErrorMessage(message) {
const contentArea = document.getElementById(this.options.containerId);
if (contentArea) {
contentArea.innerHTML = `
<div style="text-align: center; padding: 60px 20px;">
<h2 style="color: #dc3545; margin-bottom: 16px;">Error</h2>
<p style="color: #6c757d; margin-bottom: 24px;">${message}</p>
<button onclick="window.location.reload()" style="background: #022d54; color: white; padding: 10px 20px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer;">
Retry
</button>
</div>
`;
}
}
/**
* Load cost pools from APIM
*/
/**
* Load cost pools using APIM operators
*/
async loadCostPools(_token) {
try {
console.log(
'[BotManagement] 📡 Loading cost pools using APIM operators...'
);
// Ensure authentication is available
if (!this.authService?.isUserAuthenticated()) {
throw new Error('Authentication required for cost pool operations');
}
// Load cost pools using APIM operators
const response = await this.operatorsService.callOperator(
'subscription_management',
'list_cost_pools',
{
authData: this.authService.getAuthData(),
}
);
if (!response.success) {
throw new Error(
response.message || 'Failed to load cost pools from APIM operators'
);
}
this.costPools = response.data.costPools || [];
console.log(
`[BotManagement] [SUCCESS] Loaded ${this.costPools.length} cost pools using APIM operators`
);
} catch (error) {
console.error('[BotManagement] [ERROR] Error loading cost pools:', error);
throw error; // Re-throw error instead of falling back to sample data
}
}
/**
* DEPRECATED: Get fallback cost pool data
* This method should never be called in production
*/
getFallbackCostPoolData() {
console.warn(
'[BotManagement] getFallbackCostPoolData is deprecated and should not be used'
);
throw new Error(
'Fallback cost pool data is not allowed. Use APIM operators instead.'
);
}
/**
* DEPRECATED: Get fallback bot data with comprehensive properties
* This method should never be called in production
*/
getFallbackBotData() {
console.warn(
'[BotManagement] getFallbackBotData is deprecated and should not be used'
);
throw new Error(
'Fallback bot data is not allowed. Use APIM operators instead.'
);
}
/**
* Format last active timestamp
*/
formatLastActive(lastActive) {
if (!lastActive) return 'Never';
const date = new Date(lastActive);
const now = new Date();
const diffMs = now - date;
const diffMinutes = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMinutes < 1) return 'Just now';
if (diffMinutes < 60)
return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''} ago`;
if (diffHours < 24)
return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
if (diffDays < 30) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
return date.toLocaleDateString();
}
/**
* Format date string
*/
formatDate(dateString) {
if (!dateString) return 'Unknown';
return new Date(dateString).toLocaleDateString();
}
/**
* Setup event handlers
*/
setupEventHandlers() {
window.addEventListener('popstate', () => {
this.handleUrlParameters();
});
}
/**
* Handle URL parameters
*/
handleUrlParameters() {
const urlParams = new URLSearchParams(window.location.search);
const botId = urlParams.get('bot');
const action = urlParams.get('action');
if (botId) {
this.openBotDetails(botId);
} else if (action === 'create') {
this.openCreateBot();
}
}
/**
* Show error
*/
showError(error) {
const container = document.getElementById(this.options.containerId);
if (container) {
container.innerHTML = `
<div style="background: #f8f9fa; display: flex; align-items: center; justify-content: center;">
<div style="text-align: center; padding: 2rem; background: white; border-radius: 8px; border: 1px solid #e0e0e0; max-width: 500px;">
<h3 style="color: #d32f2f; margin-bottom: 1rem;">Error Loading Bot Management</h3>
<p style="color: #6c757d; margin-bottom: 1.5rem;">${error.message || error}</p>
<button onclick="location.reload()" style="padding: 10px 20px; background: #022d54; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;">
Try Again
</button>
</div>
</div>
`;
}
}
}
// BULLETPROOF EXPORT MECHANISM - Ensures class is always available
(function () {
'use strict';
// Multiple export strategies to ensure maximum compatibility
// Strategy 1: Direct window assignment (most reliable)
if (typeof window !== 'undefined') {
window.MyBotsPage = MyBotsPage;
// Also make instance globally accessible for method calls
window.myBotsPage = new MyBotsPage();
}
// Strategy 2: Module exports for Node.js environments
if (typeof module !== 'undefined' && module.exports) {
module.exports = MyBotsPage;
}
// Strategy 3: Global fallback
if (typeof global !== 'undefined') {
global.MyBotsPage = MyBotsPage;
}
// Strategy 4: AMD/RequireJS support
if (typeof define === 'function' && define.amd) {
define([], function () {
return MyBotsPage;
});
}
// Strategy 5: Self-executing verification
setTimeout(function () {
if (typeof window !== 'undefined' && !window.MyBotsPage) {
console.warn('MyBotsPage export failed, retrying...');
window.MyBotsPage = MyBotsPage;
}
}, 10);
})();