polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
908 lines (903 loc) • 33.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MonitoringDashboard = void 0;
const blessed_1 = __importDefault(require("blessed"));
const events_1 = require("events");
const grid_manager_1 = require("./grid-manager");
const component_registry_1 = require("./component-registry");
const status_component_1 = require("./status.component");
const channel_status_panel_1 = require("./channel-status.panel");
const system_resource_panel_1 = require("./system-resource.panel");
const interaction_manager_1 = require("./interaction-manager");
const help_panel_1 = require("./help-panel");
const tooltip_1 = require("./tooltip");
const context_menu_1 = require("./context-menu");
const context_menu_factory_1 = require("./context-menu-factory");
const search_panel_1 = require("./search-panel");
const layout_manager_1 = require("./layout-manager");
class MonitoringDashboard {
constructor(config) {
this.isRunning = false;
this.cleanupHandlers = [];
this.startTime = Date.now();
this.config = config;
this.eventBus = new events_1.EventEmitter();
this.eventBus.setMaxListeners(100);
this.initializeScreen();
this.setupSignalHandlers();
this.componentRegistry = new component_registry_1.ComponentRegistry(this.eventBus);
this.registerComponentFactories();
this.gridManager = new grid_manager_1.GridManager(this.screen, this.eventBus, 12, 12);
this.setupEventListeners();
this.setupInteractionManager();
}
registerComponentFactories() {
this.componentRegistry.registerFactory(status_component_1.StatusComponentFactory);
const ChannelStatusPanelFactory = {
type: 'channel-status',
create: (config, eventBus) => {
const channelConfig = {
...config,
refreshInterval: config.config?.refreshInterval || 5000,
maxChannels: config.config?.maxChannels || 100,
showColors: config.config?.showColors !== false,
columnWidths: config.config?.columnWidths || [20, 12, 10, 10, 15, 15],
sortField: config.config?.sortField || 'name',
sortOrder: config.config?.sortOrder || 'asc',
filters: config.config?.filters || {}
};
return new channel_status_panel_1.ChannelStatusPanel(channelConfig, eventBus);
},
};
this.componentRegistry.registerFactory(ChannelStatusPanelFactory);
const createPlaceholderFactory = (type) => ({
type,
create: (config, eventBus) => {
return new status_component_1.StatusComponent({
...config,
type,
}, eventBus);
},
});
const SystemResourcePanelFactory = {
type: 'system-resources',
create: (config, eventBus) => {
return new system_resource_panel_1.SystemResourcePanel(config, eventBus);
},
};
this.componentRegistry.registerFactory(SystemResourcePanelFactory);
this.componentRegistry.registerFactory(createPlaceholderFactory('stream-metrics'));
this.componentRegistry.registerFactory(createPlaceholderFactory('activity-log'));
}
initializeScreen() {
const width = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 120;
const height = process.stdout.rows && process.stdout.rows > 0 ? process.stdout.rows : 30;
const envWidth = process.env['POLYV_TERMINAL_WIDTH'];
const envHeight = process.env['POLYV_TERMINAL_HEIGHT'];
const finalWidth = envWidth && parseInt(envWidth) > 0 ? parseInt(envWidth) : width;
const finalHeight = envHeight && parseInt(envHeight) > 0 ? parseInt(envHeight) : height;
this.screen = blessed_1.default.screen({
smartCSR: true,
title: 'PolyV Live Monitoring Dashboard',
width: finalWidth,
height: finalHeight,
cursor: {
artificial: true,
shape: 'line',
blink: true,
color: 'white',
},
debug: false,
dockBorders: true,
fullUnicode: this.config.terminal?.unicodeSupport ?? true,
sendFocus: true,
warnings: false,
});
this.applyTheme(this.config.theme);
}
setupSignalHandlers() {
const handleShutdown = () => {
this.stop().then(() => {
process.exit(0);
}).catch((error) => {
console.error('Error during shutdown:', error);
process.exit(1);
});
};
process.on('SIGINT', handleShutdown);
process.on('SIGTERM', handleShutdown);
process.on('SIGQUIT', handleShutdown);
this.cleanupHandlers.push(() => {
process.off('SIGINT', handleShutdown);
process.off('SIGTERM', handleShutdown);
process.off('SIGQUIT', handleShutdown);
});
}
setupEventListeners() {
this.eventBus.on('component:error', (data) => {
this.handleComponentError(data);
});
this.eventBus.on('component:requestUpdate', (data) => {
this.handleComponentUpdateRequest(data);
});
this.eventBus.on('layout:changed', (data) => {
this.handleLayoutChange(data);
});
this.eventBus.on('grid:componentAdded', (_data) => {
this.screen.render();
});
this.eventBus.on('grid:componentRemoved', (_data) => {
this.screen.render();
});
this.screen.on('keypress', (ch, key) => {
this.handleKeyPress(ch, key);
});
this.screen.on('resize', () => {
this.handleScreenResize();
});
}
setupInteractionManager() {
const interactionConfig = {
mouseEnabled: this.config.terminal?.mouseSupport ?? true,
keyboardEnabled: true,
focusRing: {
enabled: true,
style: {
border: { fg: 'cyan' }
}
},
shortcuts: [
{
key: 'f5',
description: 'Refresh all data',
action: 'refresh',
global: true,
},
{
key: 'f1',
description: 'Show help',
action: 'help',
global: true,
},
{
key: 'ctrl+r',
description: 'Refresh screen',
action: 'refresh-screen',
global: true,
},
{
key: 'ctrl+l',
description: 'Clear and redraw screen',
action: 'clear-screen',
global: true,
},
{
key: 'ctrl+f',
description: 'Open search panel',
action: 'search',
global: true,
},
{
key: '/',
description: 'Open search panel',
action: 'search',
global: true,
},
{
key: 'f11',
description: 'Toggle fullscreen panel',
action: 'fullscreen',
global: true,
},
{
key: '1',
description: 'Switch to compact layout',
action: 'layout:compact',
global: true,
},
{
key: '2',
description: 'Switch to standard layout',
action: 'layout:standard',
global: true,
},
{
key: '3',
description: 'Switch to detailed layout',
action: 'layout:detailed',
global: true,
}
],
search: {
placeholder: 'Search channels...',
caseSensitive: false,
mode: 'fuzzy',
searchFields: ['name', 'channelId', 'status'],
maxHistory: 10,
showSuggestions: true,
},
statusBar: {
enabled: true,
position: 'bottom',
height: 1,
},
};
this.interactionManager = new interaction_manager_1.InteractionManager(this.screen, interactionConfig);
this.helpPanel = new help_panel_1.HelpPanel(this.screen, this.eventBus, {
title: 'PolyV Live Monitoring Dashboard - Help',
showShortcuts: true,
showNavigation: true,
showUsageTips: true,
});
this.helpPanel.setShortcuts(this.interactionManager.getAllShortcuts());
this.tooltip = new tooltip_1.Tooltip(this.screen, this.eventBus);
this.contextMenu = new context_menu_1.ContextMenu(this.screen, this.eventBus);
this.searchPanel = new search_panel_1.SearchPanel(this.screen, this.eventBus, {
placeholder: interactionConfig.search.placeholder,
caseSensitive: interactionConfig.search.caseSensitive,
mode: interactionConfig.search.mode,
searchFields: interactionConfig.search.searchFields,
maxHistory: interactionConfig.search.maxHistory,
showSuggestions: interactionConfig.search.showSuggestions,
});
this.layoutManager = new layout_manager_1.LayoutManager(this.screen, this.eventBus);
this.setupInteractionEventHandlers();
}
setupInteractionEventHandlers() {
this.interactionManager.on('shortcut:activated', (data) => {
this.handleShortcutAction(data.action);
});
this.interactionManager.on('focus:changed', (data) => {
this.eventBus.emit('focus:changed', data);
});
this.interactionManager.on('action:refresh', () => {
this.refresh();
});
this.interactionManager.on('action:help', () => {
this.showHelp();
});
this.interactionManager.on('action:exit', () => {
this.stop();
});
this.interactionManager.on('action:fullscreen', () => {
this.toggleFullscreen();
});
this.interactionManager.on('action:panel', (data) => {
this.switchToPanel(parseInt(data.panel) - 1);
});
this.interactionManager.on('contextmenu:requested', (data) => {
this.showContextMenu(data.x, data.y, data.context, data.componentId);
});
this.interactionManager.on('component:hover', (data) => {
this.showTooltip(data);
});
this.interactionManager.on('component:hover:clear', () => {
this.tooltip.hide();
});
this.interactionManager.on('scroll:event', (_data) => {
this.tooltip.hide();
this.contextMenu.hide();
});
this.eventBus.on('contextmenu:action', (data) => {
this.handleContextMenuAction(data.action, data.componentId, data.context);
});
this.eventBus.on('search:result:selected', (data) => {
this.handleSearchResultSelected(data.result, data.query);
});
this.eventBus.on('search:shown', () => {
this.tooltip.hide();
this.contextMenu.hide();
});
this.eventBus.on('search:hidden', () => {
});
this.eventBus.on('layout:switched', (data) => {
console.log(`Layout switched from ${data.fromLayout} to ${data.toLayout}`);
});
this.eventBus.on('layout:size:insufficient', (data) => {
console.log(`Terminal size insufficient for ${data.layout}: required ${data.required.width}x${data.required.height}, current ${data.current.width}x${data.current.height}`);
});
this.eventBus.on('panel:fullscreen:entered', (data) => {
console.log(`Panel ${data.panelId} entered fullscreen mode`);
});
this.eventBus.on('panel:fullscreen:exited', (data) => {
console.log(`Panel ${data.panelId} exited fullscreen mode`);
});
}
handleShortcutAction(action) {
switch (action) {
case 'refresh':
this.refresh();
break;
case 'refresh-screen':
this.screen.realloc();
this.screen.render();
break;
case 'clear-screen':
this.screen.clear();
this.screen.render();
break;
case 'help':
this.showHelp();
break;
case 'search':
this.showSearch();
break;
case 'fullscreen':
this.toggleFullscreen();
break;
case 'layout:compact':
this.switchLayout('compact');
break;
case 'layout:standard':
this.switchLayout('standard');
break;
case 'layout:detailed':
this.switchLayout('detailed');
break;
default:
break;
}
}
toggleFullscreen() {
const focused = this.interactionManager.getCurrentFocus();
if (focused) {
this.layoutManager.toggleFullscreen(focused);
}
}
switchLayout(layoutName) {
try {
this.layoutManager.switchLayout(layoutName);
}
catch (error) {
console.error(`Failed to switch to layout ${layoutName}:`, error);
}
}
switchToPanel(index) {
const layouts = this.gridManager.getAvailableLayouts();
if (index >= 0 && index < layouts.length) {
const layout = layouts[index];
if (layout) {
this.setLayout(layout);
}
}
}
showContextMenu(x, y, context, componentId) {
this.tooltip.hide();
const menuItems = context_menu_factory_1.ContextMenuFactory.generateMenuItems(context || 'global', componentId || '', this.getComponentContext(componentId, context));
const filteredItems = context_menu_factory_1.ContextMenuFactory.filterMenuItems(menuItems);
const enhancedItems = context_menu_factory_1.ContextMenuFactory.addShortcutsToMenuItems(filteredItems, new Map());
if (enhancedItems.length === 0) {
return;
}
this.contextMenu.show({
items: enhancedItems,
x,
y,
context,
componentId,
autoHide: true,
style: {
fg: 'white',
bg: 'black',
selectedFg: 'black',
selectedBg: 'cyan',
border: {
fg: 'gray',
},
},
});
}
getComponentContext(componentId, context) {
if (!componentId || !context) {
return {};
}
return {
componentId,
context,
status: context === 'channel-status' ? 'live' : 'unknown',
channelData: context === 'channel-status' ? { id: componentId } : undefined,
};
}
handleContextMenuAction(action, componentId, context) {
switch (action) {
case 'channel:view-details':
this.handleChannelViewDetails(componentId);
break;
case 'channel:refresh':
this.handleChannelRefresh(componentId);
break;
case 'channel:start-stream':
this.handleChannelStartStream(componentId);
break;
case 'channel:stop-stream':
this.handleChannelStopStream(componentId);
break;
case 'channel:stream-info':
this.handleChannelStreamInfo(componentId);
break;
case 'channel:copy-id':
this.handleChannelCopyId(componentId);
break;
case 'channel:export':
this.handleChannelExport(componentId);
break;
case 'channel:delete':
this.handleChannelDelete(componentId);
break;
case 'system:refresh':
this.handleSystemRefresh(componentId);
break;
case 'system:view-history':
this.handleSystemViewHistory(componentId);
break;
case 'system:configure-alerts':
this.handleSystemConfigureAlerts(componentId);
break;
case 'system:export':
this.handleSystemExport(componentId);
break;
case 'system:reset':
this.handleSystemReset(componentId);
break;
case 'system:settings':
this.handleSystemSettings(componentId);
break;
case 'dashboard:refresh-all':
this.refresh();
break;
case 'dashboard:clear':
this.screen.clear();
this.screen.render();
break;
case 'dashboard:settings':
this.handleDashboardSettings();
break;
case 'dashboard:help':
this.showHelp();
break;
case 'dashboard:exit':
this.stop();
break;
case 'component:focus':
this.handleComponentFocus(componentId);
break;
case 'component:refresh':
this.handleComponentRefresh(componentId);
break;
case 'component:fullscreen':
this.toggleFullscreen();
break;
case 'component:help':
this.showHelp();
break;
default:
console.log(`Unhandled context menu action: ${action}`);
break;
}
this.eventBus.emit('contextmenu:action:executed', {
action,
componentId,
context,
timestamp: new Date(),
});
}
showTooltip(data) {
const content = this.generateTooltipContent(data.componentType, data.componentId);
if (content) {
this.tooltip.show({
content,
x: data.x,
y: data.y,
maxWidth: 50,
autoHideDelay: 3000,
style: {
fg: 'white',
bg: 'black',
border: {
fg: 'yellow',
},
},
});
}
}
generateTooltipContent(componentType, componentId) {
switch (componentType) {
case 'channel-status':
return `Channel Status Panel\nID: ${componentId}\n\nFeatures:\n• Real-time channel monitoring\n• Status indicators\n• Quick actions\n\nTip: Right-click for menu`;
case 'system-resource':
return `System Resource Monitor\nID: ${componentId}\n\nShows:\n• CPU usage\n• Memory usage\n• Network activity\n\nTip: Scroll to view history`;
case 'stream-metrics':
return `Stream Metrics Panel\nID: ${componentId}\n\nMetrics:\n• Bitrate\n• Frame rate\n• Quality indicators\n\nTip: Click to focus panel`;
case 'help-panel':
return `Help Panel\n\nKeyboard shortcuts:\n• ESC/Q - Close\n• Arrow keys - Scroll\n• Enter - Close`;
default:
return `${componentType}\nID: ${componentId}\n\nTip: Use Tab to navigate\nPress F1 for help`;
}
}
handleComponentError(data) {
console.error(`Component error (${data.componentId}):`, data.error);
this.eventBus.emit('dashboard:error', {
type: 'component',
message: data.error,
componentId: data.componentId,
timestamp: new Date(),
});
}
handleComponentUpdateRequest(data) {
this.eventBus.emit('data:requestUpdate', {
componentId: data.componentId,
type: data.type,
timestamp: data.timestamp,
});
}
handleLayoutChange(_data) {
this.reinitializeComponents();
}
handleKeyPress(_ch, _key) {
}
handleScreenResize() {
const width = this.screen.width || 80;
const height = this.screen.height || 24;
const currentLayout = this.gridManager.getCurrentLayout();
const layoutConfig = this.gridManager.getLayoutConfig(currentLayout);
if (layoutConfig &&
(width < layoutConfig.minTerminalSize.width ||
height < layoutConfig.minTerminalSize.height)) {
this.switchToCompatibleLayout(width, height);
}
this.screen.render();
}
switchToCompatibleLayout(width, height) {
const layouts = this.gridManager.getAvailableLayouts();
for (const layoutName of layouts) {
const layoutConfig = this.gridManager.getLayoutConfig(layoutName);
if (layoutConfig &&
width >= layoutConfig.minTerminalSize.width &&
height >= layoutConfig.minTerminalSize.height) {
this.setLayout(layoutName);
break;
}
}
}
applyTheme(themeName) {
const themes = {
'default': {
colors: {
primary: 'blue',
secondary: 'cyan',
background: 'black',
foreground: 'white',
accent: 'yellow',
error: 'red',
warning: 'yellow',
success: 'green',
info: 'blue',
muted: 'gray',
highlight: 'yellow',
border: 'white',
selection: 'blue',
},
},
dark: {
colors: {
primary: 'white',
secondary: 'gray',
background: 'black',
foreground: 'white',
accent: 'cyan',
error: 'red',
warning: 'yellow',
success: 'green',
info: 'blue',
muted: 'gray',
highlight: 'cyan',
border: 'gray',
selection: 'white',
},
},
};
const theme = themes[themeName] || themes['default'];
this.eventBus.emit('theme:change', {
theme: themeName,
config: theme,
timestamp: new Date(),
});
}
reinitializeComponents() {
this.componentRegistry.removeAllComponents();
const layout = this.gridManager.getCurrentLayout();
const layoutConfig = this.gridManager.getLayoutConfig(layout);
if (layoutConfig) {
for (const componentLayout of layoutConfig.components) {
try {
const config = {
type: componentLayout.type,
position: componentLayout.position,
size: componentLayout.size,
config: componentLayout.config,
visible: true,
priority: 1,
};
const component = this.componentRegistry.createComponent(config);
this.gridManager.addComponent(component, componentLayout.position);
if (component && typeof component.canFocus === 'function') {
this.interactionManager.registerComponent(component);
}
if (component) {
this.layoutManager.registerPanel(componentLayout.type, component);
}
}
catch (error) {
console.error(`Failed to create component ${componentLayout.type}:`, error);
}
}
}
}
handleChannelViewDetails(componentId) {
console.log(`Viewing details for channel: ${componentId}`);
}
handleChannelRefresh(componentId) {
console.log(`Refreshing channel: ${componentId}`);
}
handleChannelStartStream(componentId) {
console.log(`Starting stream for channel: ${componentId}`);
}
handleChannelStopStream(componentId) {
console.log(`Stopping stream for channel: ${componentId}`);
}
handleChannelStreamInfo(componentId) {
console.log(`Viewing stream info for channel: ${componentId}`);
}
handleChannelCopyId(componentId) {
console.log(`Copying channel ID: ${componentId}`);
}
handleChannelExport(componentId) {
console.log(`Exporting channel data: ${componentId}`);
}
handleChannelDelete(componentId) {
console.log(`Deleting channel: ${componentId}`);
}
handleSystemRefresh(componentId) {
console.log(`Refreshing system metrics: ${componentId}`);
}
handleSystemViewHistory(componentId) {
console.log(`Viewing system history: ${componentId}`);
}
handleSystemConfigureAlerts(componentId) {
console.log(`Configuring alerts: ${componentId}`);
}
handleSystemExport(componentId) {
console.log(`Exporting system metrics: ${componentId}`);
}
handleSystemReset(componentId) {
console.log(`Resetting system counters: ${componentId}`);
}
handleSystemSettings(componentId) {
console.log(`Opening system settings: ${componentId}`);
}
handleDashboardSettings() {
console.log('Opening dashboard settings');
}
handleComponentFocus(componentId) {
if (componentId) {
this.interactionManager.setFocus(componentId);
}
}
handleComponentRefresh(componentId) {
console.log(`Refreshing component: ${componentId}`);
}
showHelp() {
this.helpPanel.setShortcuts(this.interactionManager.getAllShortcuts());
this.helpPanel.show();
}
showSearch() {
const searchData = this.gatherSearchableData();
this.searchPanel.setDataSource(searchData);
this.searchPanel.show();
}
gatherSearchableData() {
const searchData = [];
const components = this.componentRegistry.getAllComponents();
components.forEach((component, componentId) => {
try {
if (typeof component.getSearchableData === 'function') {
const componentData = component.getSearchableData();
if (Array.isArray(componentData)) {
componentData.forEach(item => {
searchData.push({
...item,
componentId,
componentType: component.constructor.name,
});
});
}
}
}
catch (error) {
}
});
if (searchData.length === 0) {
searchData.push({
id: 'ch001',
name: 'Live Stream Channel 1',
status: 'live',
type: 'channel',
componentType: 'ChannelStatusPanel',
}, {
id: 'ch002',
name: 'Test Channel',
status: 'offline',
type: 'channel',
componentType: 'ChannelStatusPanel',
}, {
id: 'sys001',
name: 'System Resources',
status: 'active',
type: 'system',
componentType: 'SystemResourcePanel',
});
}
return searchData;
}
handleSearchResultSelected(result, query) {
console.log(`Search result selected: ${result.item.name} (query: ${query})`);
if (result.item.componentId) {
this.interactionManager.setFocus(result.item.componentId);
}
this.eventBus.emit('search:action:executed', {
result,
query,
action: 'focus-component',
timestamp: new Date(),
});
this.searchPanel.hide();
}
async start() {
if (this.isRunning) {
throw new Error('Dashboard is already running');
}
try {
this.isRunning = true;
this.createWelcomeScreen();
this.eventBus.emit('dashboard:started', {
timestamp: new Date(),
config: this.config,
});
this.screen.render();
this.startRefreshCycle();
}
catch (error) {
this.isRunning = false;
throw error;
}
}
createWelcomeScreen() {
const welcomeBox = blessed_1.default.box({
top: 'center',
left: 'center',
width: '80%',
height: '60%',
content: this.getWelcomeMessage(),
tags: true,
border: {
type: 'line',
},
style: {
fg: 'white',
bg: 'black',
border: {
fg: 'cyan',
},
},
label: ' PolyV Live Monitoring Dashboard ',
});
this.screen.append(welcomeBox);
}
getWelcomeMessage() {
return `
{center}Welcome to PolyV Live Monitoring Dashboard{/center}
{cyan-fg}Dashboard Status:{/cyan-fg}
• Status: Running
• Layout: ${this.config.layout}
• Theme: ${this.config.theme}
• Refresh Interval: ${this.config.refreshInterval}ms
{cyan-fg}Available Commands:{/cyan-fg}
• Press 'q', 'Escape', or 'Ctrl+C' to exit
• Press 'Ctrl+R' to refresh
• Press 'Ctrl+L' to clear screen
• Press '?' or 'h' for help
{cyan-fg}Features (Coming Soon):{/cyan-fg}
• Stream metrics monitoring
• Channel status tracking
• System resource monitoring
• Activity logging
{yellow-fg}Note: This is a basic interface. Full grid-based dashboard is under development.{/yellow-fg}
Press any key to continue monitoring...
`;
}
async stop() {
if (!this.isRunning) {
return;
}
try {
this.isRunning = false;
this.stopRefreshCycle();
this.componentRegistry.removeAllComponents();
this.gridManager.destroy();
if (this.interactionManager) {
this.interactionManager.destroy();
}
if (this.searchPanel) {
this.searchPanel.destroy();
}
if (this.layoutManager) {
this.layoutManager.destroy();
}
this.cleanupHandlers.forEach(handler => handler());
this.cleanupHandlers = [];
this.screen.destroy();
this.eventBus.emit('dashboard:stopped', {
timestamp: new Date(),
});
}
catch (error) {
console.error('Error stopping dashboard:', error);
}
}
refresh() {
this.eventBus.emit('dashboard:refresh', {
timestamp: new Date(),
});
this.screen.render();
}
setLayout(layoutName) {
try {
this.gridManager.setLayout(layoutName);
this.reinitializeComponents();
this.screen.render();
}
catch (error) {
console.error(`Failed to set layout ${layoutName}:`, error);
}
}
getStatus() {
return {
isRunning: this.isRunning,
layout: this.gridManager.getCurrentLayout(),
components: this.componentRegistry.getComponentCount(),
uptime: this.isRunning ? Date.now() - this.startTime : 0,
};
}
startRefreshCycle() {
this.refreshInterval = setInterval(() => {
if (this.isRunning) {
this.eventBus.emit('dashboard:tick', {
timestamp: new Date(),
});
}
}, this.config.refreshInterval);
}
stopRefreshCycle() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = undefined;
}
}
getComponentRegistry() {
return this.componentRegistry;
}
getGridManager() {
return this.gridManager;
}
getEventBus() {
return this.eventBus;
}
getScreen() {
return this.screen;
}
}
exports.MonitoringDashboard = MonitoringDashboard;
//# sourceMappingURL=monitoring-dashboard.js.map