@memberjunction/ng-dashboards 6.2.0-edge.4 → 6.2.0-edge.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AI/components/vectors/sensitive-fields.d.ts +53 -0
- package/dist/AI/components/vectors/sensitive-fields.d.ts.map +1 -0
- package/dist/AI/components/vectors/sensitive-fields.js +226 -0
- package/dist/AI/components/vectors/sensitive-fields.js.map +1 -0
- package/dist/AI/components/vectors/vector-document-rules.d.ts +126 -0
- package/dist/AI/components/vectors/vector-document-rules.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-document-rules.js +168 -0
- package/dist/AI/components/vectors/vector-document-rules.js.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +49 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +428 -230
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/Communication/communication-agent-context.d.ts +11 -1
- package/dist/Communication/communication-agent-context.d.ts.map +1 -1
- package/dist/Communication/communication-agent-context.js +15 -0
- package/dist/Communication/communication-agent-context.js.map +1 -1
- package/dist/Communication/communication-dashboard.component.d.ts.map +1 -1
- package/dist/Communication/communication-dashboard.component.js +5 -0
- package/dist/Communication/communication-dashboard.component.js.map +1 -1
- package/dist/Communication/communication-providers-resource.component.d.ts +27 -1
- package/dist/Communication/communication-providers-resource.component.d.ts.map +1 -1
- package/dist/Communication/communication-providers-resource.component.js +722 -78
- package/dist/Communication/communication-providers-resource.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +25 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +74 -5
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DevTools/app-state-inspector.component.d.ts.map +1 -1
- package/dist/DevTools/app-state-inspector.component.js +1 -0
- package/dist/DevTools/app-state-inspector.component.js.map +1 -1
- package/dist/DevTools/class-registry.component.d.ts.map +1 -1
- package/dist/DevTools/class-registry.component.js +1 -0
- package/dist/DevTools/class-registry.component.js.map +1 -1
- package/dist/DevTools/event-monitor.component.d.ts.map +1 -1
- package/dist/DevTools/event-monitor.component.js +2 -0
- package/dist/DevTools/event-monitor.component.js.map +1 -1
- package/dist/DevTools/graphql-console.component.d.ts.map +1 -1
- package/dist/DevTools/graphql-console.component.js.map +1 -1
- package/dist/DevTools/grid-width-lab.component.d.ts.map +1 -1
- package/dist/DevTools/grid-width-lab.component.js +1 -0
- package/dist/DevTools/grid-width-lab.component.js.map +1 -1
- package/dist/DevTools/layout-inspector.component.d.ts.map +1 -1
- package/dist/DevTools/layout-inspector.component.js +1 -0
- package/dist/DevTools/layout-inspector.component.js.map +1 -1
- package/dist/DevTools/lazy-module-status.component.d.ts.map +1 -1
- package/dist/DevTools/lazy-module-status.component.js +1 -0
- package/dist/DevTools/lazy-module-status.component.js.map +1 -1
- package/dist/DevTools/settings-explorer.component.d.ts.map +1 -1
- package/dist/DevTools/settings-explorer.component.js +1 -0
- package/dist/DevTools/settings-explorer.component.js.map +1 -1
- package/dist/DevTools/tab-strip-lab.component.d.ts.map +1 -1
- package/dist/DevTools/tab-strip-lab.component.js +1 -0
- package/dist/DevTools/tab-strip-lab.component.js.map +1 -1
- package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts +5 -0
- package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts.map +1 -1
- package/dist/EntityAdmin/entity-admin-dashboard.component.js +8 -5
- package/dist/EntityAdmin/entity-admin-dashboard.component.js.map +1 -1
- package/dist/Integration/components/overview/overview.component.js +2 -2
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +2 -2
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts +11 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js +24 -2
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts +16 -2
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts.map +1 -1
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js +87 -33
- package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js.map +1 -1
- package/dist/LoginStudio/login-studio-inspector.component.d.ts +130 -0
- package/dist/LoginStudio/login-studio-inspector.component.d.ts.map +1 -0
- package/dist/LoginStudio/login-studio-inspector.component.js +781 -0
- package/dist/LoginStudio/login-studio-inspector.component.js.map +1 -0
- package/dist/LoginStudio/login-studio-resource.component.d.ts +153 -0
- package/dist/LoginStudio/login-studio-resource.component.d.ts.map +1 -0
- package/dist/LoginStudio/login-studio-resource.component.js +857 -0
- package/dist/LoginStudio/login-studio-resource.component.js.map +1 -0
- package/dist/Workflows/components/workflow-runs-resource.component.js +4 -4
- package/dist/Workflows/components/workflow-runs-resource.component.js.map +1 -1
- package/dist/core-dashboards.module.d.ts +2 -1
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +11 -3
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +72 -69
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-admin-dashboard.component.js","sourceRoot":"","sources":["../../src/EntityAdmin/entity-admin-dashboard.component.ts","../../src/EntityAdmin/entity-admin-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';\nimport { BaseDashboard } from '@memberjunction/ng-shared';\nimport { RegisterClass , UUIDsEqual } from '@memberjunction/global';\nimport { EntityInfo, CompositeKey, Metadata } from '@memberjunction/core';\nimport { Subject } from 'rxjs';\nimport { debounceTime } from 'rxjs/operators';\n\nimport { ERDCompositeComponent, ERDCompositeState } from '@memberjunction/ng-entity-relationship-diagram';\nimport { ResourceData, MJUserSettingEntity, UserInfoEngine } from '@memberjunction/core-entities';\n\nimport {\n BuildEntityAdminAgentContext,\n BuildEntityNotFoundError,\n EntityDisplayName,\n ResolveEntityByIdOrName,\n EntityNameCandidate,\n RelatedEntitySummary,\n SchemaGroupSummary,\n} from './entity-admin-agent-context';\nimport { AgentToolResult, ValidateStringParam } from '../shared/agent-tool-validation';\n\n/** Settings key for ERD state persistence */\nconst ERD_SETTINGS_KEY = 'MJ.Admin.Entity.ERD';\n\n@Component({\n standalone: false,\n selector: 'mj-entity-admin-dashboard',\n templateUrl: './entity-admin-dashboard.component.html',\n styleUrls: ['./entity-admin-dashboard.component.css']\n})\n@RegisterClass(BaseDashboard, 'EntityAdmin')\nexport class EntityAdminDashboardComponent extends BaseDashboard implements AfterViewInit, OnDestroy {\n @ViewChild('erdComposite', { static: false }) ErdComposite!: ERDCompositeComponent;\n\n /** @deprecated Use {@link ErdComposite}. */\n get erdComposite(): ERDCompositeComponent {\n return this.ErdComposite;\n }\n /** @deprecated Use {@link ErdComposite}. */\n set erdComposite(value: ERDCompositeComponent) {\n this.ErdComposite = value;\n }\n\n public isLoading = false;\n public IsRefreshingERD = false;\n\n /** @deprecated Use {@link IsRefreshingERD}. */\n public get isRefreshingERD() {\n return this.IsRefreshingERD;\n }\n /** @deprecated Use {@link IsRefreshingERD}. */\n public set isRefreshingERD(value) {\n this.IsRefreshingERD = value;\n }\n public LoadingMessage = '';\n\n /** @deprecated Use {@link LoadingMessage}. */\n public get loadingMessage() {\n return this.LoadingMessage;\n }\n /** @deprecated Use {@link LoadingMessage}. */\n public set loadingMessage(value) {\n this.LoadingMessage = value;\n }\n public error: string | null = null;\n\n // Filter panel visibility for header controls\n public FilterPanelVisible = true;\n\n /** @deprecated Use {@link FilterPanelVisible}. */\n public get filterPanelVisible() {\n return this.FilterPanelVisible;\n }\n /** @deprecated Use {@link FilterPanelVisible}. */\n public set filterPanelVisible(value) {\n this.FilterPanelVisible = value;\n }\n public SelectedEntity: EntityInfo | null = null;\n\n /** @deprecated Use {@link SelectedEntity}. */\n public get selectedEntity(): EntityInfo | null {\n return this.SelectedEntity;\n }\n /** @deprecated Use {@link SelectedEntity}. */\n public set selectedEntity(value: EntityInfo | null) {\n this.SelectedEntity = value;\n }\n public FilteredEntities: EntityInfo[] = [];\n\n /** @deprecated Use {@link FilteredEntities}. */\n public get filteredEntities(): EntityInfo[] {\n return this.FilteredEntities;\n }\n /** @deprecated Use {@link FilteredEntities}. */\n public set filteredEntities(value: EntityInfo[]) {\n this.FilteredEntities = value;\n }\n\n /** Total unfiltered entity count — feeds the chrome's X-of-Y badge. */\n public get TotalEntityCount(): number {\n return this.ErdComposite?.entities?.length ?? 0;\n }\n\n // State management\n private userStateChangeSubject = new Subject<ERDCompositeState>();\n private hasLoadedUserState = false;\n private metadata = this.ProviderToUse;\n private userSettingEntity: MJUserSettingEntity | null = null;\n\n ngAfterViewInit(): void {\n // Setup debounced state persistence to MJ: User Settings\n this.userStateChangeSubject.pipe(\n debounceTime(1000)\n ).subscribe(state => {\n this.saveStateToUserSettings(state);\n });\n\n // Publish the initial agent context and register the (read-only) client tools\n // the AI agent can invoke against this surface. Ongoing re-emit happens in\n // onStateChange (every ERD state change).\n this.publishAgentContext();\n this.registerAgentClientTools();\n }\n\n async GetResourceDisplayName(data: ResourceData): Promise<string> {\n return \"Entity Administration\"\n }\n\n override ngOnDestroy(): void {\n this.userStateChangeSubject.complete();\n super.ngOnDestroy();\n }\n\n protected initDashboard(): void {\n // Initialize dashboard - called by BaseDashboard\n }\n\n protected loadData(): void {\n // Data loading is handled by ERDCompositeComponent\n }\n\n public ToggleFilterPanel(): void {\n this.FilterPanelVisible = !this.FilterPanelVisible;\n if (this.ErdComposite) {\n this.ErdComposite.onToggleFilterPanel();\n }\n }\n\n /** @deprecated Use {@link ToggleFilterPanel}. */\n public toggleFilterPanel(): void {\n return this.ToggleFilterPanel();\n }\n\n public OnStateChange(state: ERDCompositeState): void {\n // Update local state to keep header controls in sync\n this.FilterPanelVisible = state.filterPanelVisible;\n this.FilteredEntities = this.ErdComposite?.filteredEntities || [];\n\n if (state.selectedEntityId && this.ErdComposite) {\n this.SelectedEntity = this.ErdComposite.entities.find(e => UUIDsEqual(e.ID, state.selectedEntityId)) || null;\n } else {\n this.SelectedEntity = null;\n }\n\n // Load user state when data becomes available for the first time\n if (this.ErdComposite?.isDataLoaded && !this.hasLoadedUserState) {\n this.hasLoadedUserState = true;\n this.loadStateFromUserSettings();\n }\n\n // Keep the AI agent's view of this surface in sync with every ERD state change\n // (entity selection, filter panel visibility, filtered count, etc.).\n this.publishAgentContext();\n }\n\n /** @deprecated Use {@link OnStateChange}. */\n public onStateChange(state: ERDCompositeState): void {\n return this.OnStateChange(state);\n }\n\n public OnUserStateChange(state: ERDCompositeState): void {\n // Queue state for debounced persistence\n this.userStateChangeSubject.next(state);\n }\n\n /** @deprecated Use {@link OnUserStateChange}. */\n public onUserStateChange(state: ERDCompositeState): void {\n return this.OnUserStateChange(state);\n }\n\n public OnEntityOpened(entity: EntityInfo): void {\n this.OpenEntity(entity);\n }\n\n /** @deprecated Use {@link OnEntityOpened}. */\n public onEntityOpened(entity: EntityInfo): void {\n return this.OnEntityOpened(entity);\n }\n\n public OnOpenRecord(event: {EntityName: string, RecordID: string}): void {\n this.OpenEntityRecord.emit({\n EntityName: event.EntityName,\n // The ERD emits records of arbitrary entities — resolve the key against the entity's metadata\n RecordPKey: CompositeKey.FromURLSegment(this.metadata.EntityByName(event.EntityName), event.RecordID)\n });\n }\n\n /** @deprecated Use {@link OnOpenRecord}. */\n public onOpenRecord(event: {EntityName: string, RecordID: string}): void {\n return this.OnOpenRecord(event);\n }\n\n public OpenEntity(entity: EntityInfo): void {\n this.Interaction.emit({\n type: 'openEntity',\n entity: entity,\n data: { entityId: entity.ID, entityName: entity.Name }\n });\n }\n\n /** @deprecated Use {@link OpenEntity}. */\n public openEntity(entity: EntityInfo): void {\n return this.OpenEntity(entity);\n }\n\n /**\n * Load ERD state from MJ: User Settings entity using UserInfoEngine for cached access\n */\n private async loadStateFromUserSettings(): Promise<void> {\n try {\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) {\n this.NotifyLoadComplete();\n return;\n }\n\n const engine = UserInfoEngine.Instance;\n\n // Find setting from cached user settings\n const setting = engine.UserSettings.find(s => s.Setting === ERD_SETTINGS_KEY);\n\n if (setting) {\n this.userSettingEntity = setting;\n if (this.userSettingEntity.Value) {\n const savedState = JSON.parse(this.userSettingEntity.Value) as Partial<ERDCompositeState>;\n if (this.ErdComposite) {\n this.ErdComposite.loadUserState(savedState);\n }\n }\n }\n\n this.NotifyLoadComplete();\n } catch (error) {\n console.warn('Failed to load ERD state from User Settings:', error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Save ERD state to MJ: User Settings entity using UserInfoEngine for cached lookup\n */\n private async saveStateToUserSettings(state: ERDCompositeState): Promise<void> {\n try {\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) return;\n\n // Find existing setting from cached user settings if not already loaded\n if (!this.userSettingEntity) {\n const engine = UserInfoEngine.Instance;\n const setting = engine.UserSettings.find(s => s.Setting === ERD_SETTINGS_KEY);\n\n if (setting) {\n this.userSettingEntity = setting;\n } else {\n this.userSettingEntity = await this.metadata.GetEntityObject<MJUserSettingEntity>('MJ: User Settings');\n this.userSettingEntity.UserID = userId;\n this.userSettingEntity.Setting = ERD_SETTINGS_KEY;\n }\n }\n\n // Save the state as JSON\n this.userSettingEntity.Value = JSON.stringify(state);\n await this.userSettingEntity.Save();\n } catch (error) {\n console.warn('Failed to save ERD state to User Settings:', error);\n }\n }\n\n // ========================================\n // AI AGENT CONTEXT & CLIENT TOOLS\n //\n // 🔒 SAFETY BOUNDARY: The Entity Admin surface is a metadata BROWSER (the ERD\n // entity explorer). The context published and the tools registered below are\n // STRICTLY READ-ONLY / NAVIGATIONAL — select/clear an entity, toggle the filter\n // panel, refresh the diagram. NO tool here (and none added in future) may edit,\n // create, or delete entity metadata, alter schema, or perform any other mutation.\n // The agent browses; the user makes changes from the dedicated entity forms.\n // ========================================\n\n /**\n * Publish the current Entity Admin ERD-browser state to the AI agent via\n * NavigationService. Reports — at Data-Explorer depth — the entity counts (total +\n * filtered), the active filter values (schema / search / status), the current\n * selection (id + registered name + DISPLAY name + schema + description + field count\n * + a bounded relationship summary), filter-panel visibility, a bounded list of\n * available entity DISPLAY names, and the schema-grouping landscape. The shaping lives\n * in the pure {@link buildEntityAdminAgentContext} helper so it stays unit-testable.\n * Called on init (ngAfterViewInit) and on every ERD state change (onStateChange).\n */\n private publishAgentContext(): void {\n const filters = this.ErdComposite?.filters;\n const context = BuildEntityAdminAgentContext({\n TotalEntityCount: this.TotalEntityCount,\n FilteredEntityCount: this.FilteredEntities.length,\n SelectedEntityId: this.SelectedEntity?.ID ?? null,\n SelectedEntityName: this.SelectedEntity?.Name ?? null,\n SelectedEntityDisplayName: this.SelectedEntity\n ? EntityDisplayName(this.SelectedEntity.Name, this.SelectedEntity.DisplayName)\n : null,\n SelectedEntitySchema: this.SelectedEntity?.SchemaName ?? null,\n SelectedEntityDescription: this.SelectedEntity?.Description ?? null,\n SelectedEntityFieldCount: this.SelectedEntity?.Fields?.length ?? null,\n RelatedEntities: this.buildRelatedEntitySummaries(),\n FilterPanelVisible: this.FilterPanelVisible,\n SchemaFilter: filters?.schemaName ?? null,\n SearchText: filters?.entityName ?? '',\n StatusFilter: filters?.entityStatus ?? null,\n AvailableEntityNames: this.FilteredEntities.map(e => EntityDisplayName(e.Name, e.DisplayName)),\n SchemaGroups: this.buildSchemaGroupSummaries(),\n });\n this.navigationService.SetAgentContext(this, context);\n }\n\n /** Related-entity summaries for the selected entity (display name + relationship type). */\n private buildRelatedEntitySummaries(): RelatedEntitySummary[] {\n if (!this.SelectedEntity) {\n return [];\n }\n return this.SelectedEntity.RelatedEntities.map(re => ({\n Name: re.RelatedEntity ?? re.RelatedEntityID ?? '(unknown)',\n RelationshipType: re.Type ?? 'Related',\n }));\n }\n\n /** Schema-grouping summaries across the currently-visible (filtered) entities. */\n private buildSchemaGroupSummaries(): SchemaGroupSummary[] {\n const counts = new Map<string, number>();\n for (const e of this.FilteredEntities) {\n const schema = e.SchemaName || '(no schema)';\n counts.set(schema, (counts.get(schema) ?? 0) + 1);\n }\n return Array.from(counts.entries())\n .map(([SchemaName, EntityCount]) => ({ SchemaName, EntityCount }))\n .sort((a, b) => b.EntityCount - a.EntityCount);\n }\n\n /** The ERD's loaded entities, narrowed to the resolver's structural shape. */\n private get entityCandidates(): EntityNameCandidate[] {\n return this.ErdComposite?.entities ?? [];\n }\n\n /**\n * Register the (read-only / navigational) client tools the AI agent can invoke\n * against the Entity Admin ERD. Each handler delegates to the same component or\n * ERD method a user interaction would call, and returns `{ Success: true }` on\n * success or `{ Success: false, ErrorMessage }` on failure. Handlers never throw.\n *\n * Tools (browse only — see SAFETY BOUNDARY above):\n * - SelectEntity: select/focus an entity by its ID, registered Name, or DISPLAY name.\n * - ToggleFilterPanel: show/hide the ERD filter panel.\n * - ClearEntitySelection: deselect the currently selected entity.\n * - RefreshERD: re-render the ERD diagram.\n * - FilterEntities: narrow the ERD by schema and/or a name search.\n * - NavigateToEntityRecord: open an entity's record list/view (read-only navigation).\n */\n private registerAgentClientTools(): void {\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'SelectEntity',\n Description: 'Select and focus an entity in the ERD by its ID, registered name, or the display name shown on screen (e.g. \"AI Models\" rather than \"MJ: AI Models\"). Read-only — opens nothing for edit.',\n ParameterSchema: { type: 'object', properties: { entity: { type: 'string', description: 'Entity ID, registered name, or display name.' } }, required: ['entity'] },\n Handler: async (params: Record<string, unknown>) => this.toolSelectEntity(params),\n },\n {\n Name: 'ToggleFilterPanel',\n Description: 'Show or hide the ERD entity filter panel.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n this.ToggleFilterPanel();\n this.publishAgentContext();\n return { Success: true };\n },\n },\n {\n Name: 'ClearEntitySelection',\n Description: 'Clear the currently selected entity in the ERD.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => this.toolClearEntitySelection(),\n },\n {\n Name: 'RefreshERD',\n Description: 'Re-render the ERD diagram (no data is modified).',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => this.toolRefreshERD(),\n },\n {\n Name: 'FilterEntities',\n Description: 'Narrow the entities shown in the ERD by schema name and/or an entity-name search. Pass an empty string for a field to clear it; pass neither to reset all filters. No data is modified.',\n ParameterSchema: {\n type: 'object',\n properties: {\n schema: { type: 'string', description: 'Schema name to filter by (empty string clears it).' },\n search: { type: 'string', description: 'Entity-name search text (empty string clears it).' },\n },\n },\n Handler: async (params: Record<string, unknown>) => this.toolFilterEntities(params),\n },\n {\n Name: 'NavigateToEntityRecord',\n Description: 'Open the record list / data view for an entity (by ID, registered name, or display name) for viewing. Read-only navigation — opens nothing for edit and creates no records.',\n ParameterSchema: { type: 'object', properties: { entity: { type: 'string', description: 'Entity ID, registered name, or display name.' } }, required: ['entity'] },\n Handler: async (params: Record<string, unknown>) => this.toolNavigateToEntityRecord(params),\n },\n ]);\n }\n\n /** Resolve an entity by ID / registered name / display name and select/focus it. */\n private toolSelectEntity(params: Record<string, unknown>): AgentToolResult {\n const validated = ValidateStringParam(params['entity'], 'entity');\n if (!validated.ok) {\n return validated.result;\n }\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n const candidates = this.entityCandidates;\n const match = ResolveEntityByIdOrName(validated.value, candidates);\n if (!match) {\n return { Success: false, ErrorMessage: BuildEntityNotFoundError(validated.value, candidates) };\n }\n const entity = this.ErdComposite.entities.find(e => UUIDsEqual(e.ID, match.ID));\n if (!entity) {\n return { Success: false, ErrorMessage: BuildEntityNotFoundError(validated.value, candidates) };\n }\n this.ErdComposite.onEntitySelected(entity);\n return { Success: true, ErrorMessage: undefined };\n }\n\n /** Deselect the currently selected entity in the ERD. */\n private toolClearEntitySelection(): AgentToolResult {\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n this.ErdComposite.onEntityDeselected();\n return { Success: true };\n }\n\n /** Re-render the ERD diagram. */\n private toolRefreshERD(): AgentToolResult {\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n this.ErdComposite.refreshERD();\n return { Success: true };\n }\n\n /** Narrow the ERD by schema and/or a name search (read-only — only changes what's shown). */\n private toolFilterEntities(params: Record<string, unknown>): AgentToolResult {\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n const schemaRaw = params['schema'];\n const searchRaw = params['search'];\n\n // Neither provided → reset all filters.\n if (schemaRaw === undefined && searchRaw === undefined) {\n this.ErdComposite.onResetFilters();\n this.publishAgentContext();\n return { Success: true };\n }\n\n const next = { ...this.ErdComposite.filters };\n if (schemaRaw !== undefined) {\n const validated = ValidateStringParam(schemaRaw, 'schema');\n if (!validated.ok) {\n return validated.result;\n }\n const schema = validated.value.trim();\n // Tolerant case-insensitive match against the known schemas; empty clears it.\n if (!schema) {\n next.schemaName = null;\n } else {\n const known = Array.from(new Set(this.entityCandidates.map(e => (e as { SchemaName?: string }).SchemaName).filter((s): s is string => !!s)));\n const resolved = known.find(s => s.toLowerCase() === schema.toLowerCase());\n if (!resolved) {\n return { Success: false, ErrorMessage: `No schema matches \"${schema}\". Available schemas: ${known.join(', ') || '(none)'}.` };\n }\n next.schemaName = resolved;\n }\n }\n if (searchRaw !== undefined) {\n const validated = ValidateStringParam(searchRaw, 'search');\n if (!validated.ok) {\n return validated.result;\n }\n next.entityName = validated.value;\n }\n\n this.ErdComposite.onFiltersChange(next);\n this.publishAgentContext();\n return { Success: true };\n }\n\n /** Open an entity's record list / data view for viewing (read-only navigation). */\n private toolNavigateToEntityRecord(params: Record<string, unknown>): AgentToolResult {\n const validated = ValidateStringParam(params['entity'], 'entity');\n if (!validated.ok) {\n return validated.result;\n }\n const candidates = this.entityCandidates;\n const match = ResolveEntityByIdOrName(validated.value, candidates);\n if (!match) {\n return { Success: false, ErrorMessage: BuildEntityNotFoundError(validated.value, candidates) };\n }\n // Open the entity's dynamic record view (read-only navigation — no edit, no create).\n this.navigationService.OpenDynamicView(match.Name);\n return { Success: true };\n }\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Entity Admin\"\n Icon=\"fa-solid fa-database\"\n Subtitle=\"Browse and configure entity definitions\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions §2. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredEntities.length\"\n [Total]=\"TotalEntityCount\"\n Label=\"entities\">\n </mj-stat-badge>\n </div>\n <div actions>\n <button\n mjButton\n [variant]=\"filterPanelVisible ? 'primary' : 'secondary'\"\n size=\"sm\"\n (click)=\"toggleFilterPanel()\"\n title=\"Toggle Filters\">\n <i class=\"fa-solid fa-filter\"></i> <span class=\"action-btn-label\">Filters</span>\n </button>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n @if (isLoading) {\n <div class=\"loading-container\">\n <mj-loading [text]=\"loadingMessage\" size=\"large\"></mj-loading>\n </div>\n }\n\n @if (error) {\n <div class=\"error-container\">\n <p class=\"error-message\">{{ error }}</p>\n </div>\n }\n\n @if (!isLoading && !error) {\n <div class=\"dashboard-content\">\n <mj-erd-composite\n #erdComposite\n [isRefreshingERD]=\"isRefreshingERD\"\n (stateChange)=\"onStateChange($event)\"\n (userStateChange)=\"onUserStateChange($event)\"\n (entityOpened)=\"onEntityOpened($event)\"\n (openRecord)=\"onOpenRecord($event)\"\n >\n </mj-erd-composite>\n </div>\n }\n </mj-page-body>\n</mj-page-layout>"]}
|
|
1
|
+
{"version":3,"file":"entity-admin-dashboard.component.js","sourceRoot":"","sources":["../../src/EntityAdmin/entity-admin-dashboard.component.ts","../../src/EntityAdmin/entity-admin-dashboard.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';\nimport { BaseDashboard } from '@memberjunction/ng-shared';\nimport { RegisterClass , UUIDsEqual } from '@memberjunction/global';\nimport { EntityInfo, CompositeKey, Metadata } from '@memberjunction/core';\nimport { Subject } from 'rxjs';\nimport { debounceTime } from 'rxjs/operators';\n\nimport { ERDCompositeComponent, ERDCompositeState } from '@memberjunction/ng-entity-relationship-diagram';\nimport { ResourceData, MJUserSettingEntity, UserInfoEngine } from '@memberjunction/core-entities';\n\nimport {\n BuildEntityAdminAgentContext,\n BuildEntityNotFoundError,\n EntityDisplayName,\n ResolveEntityByIdOrName,\n EntityNameCandidate,\n RelatedEntitySummary,\n SchemaGroupSummary,\n} from './entity-admin-agent-context';\nimport { AgentToolResult, ValidateStringParam } from '../shared/agent-tool-validation';\n\n/** Settings key for ERD state persistence */\nconst ERD_SETTINGS_KEY = 'MJ.Admin.Entity.ERD';\n\n@Component({\n standalone: false,\n selector: 'mj-entity-admin-dashboard',\n templateUrl: './entity-admin-dashboard.component.html',\n styleUrls: ['./entity-admin-dashboard.component.css']\n})\n@RegisterClass(BaseDashboard, 'EntityAdmin')\nexport class EntityAdminDashboardComponent extends BaseDashboard implements AfterViewInit, OnDestroy {\n @ViewChild('erdComposite', { static: false }) ErdComposite!: ERDCompositeComponent;\n\n /** @deprecated Use {@link ErdComposite}. */\n get erdComposite(): ERDCompositeComponent {\n return this.ErdComposite;\n }\n /** @deprecated Use {@link ErdComposite}. */\n set erdComposite(value: ERDCompositeComponent) {\n this.ErdComposite = value;\n }\n\n public isLoading = false;\n public IsRefreshingERD = false;\n\n /** @deprecated Use {@link IsRefreshingERD}. */\n public get isRefreshingERD() {\n return this.IsRefreshingERD;\n }\n /** @deprecated Use {@link IsRefreshingERD}. */\n public set isRefreshingERD(value) {\n this.IsRefreshingERD = value;\n }\n public LoadingMessage = '';\n\n /** @deprecated Use {@link LoadingMessage}. */\n public get loadingMessage() {\n return this.LoadingMessage;\n }\n /** @deprecated Use {@link LoadingMessage}. */\n public set loadingMessage(value) {\n this.LoadingMessage = value;\n }\n public error: string | null = null;\n\n // Filter panel visibility for header controls\n public FilterPanelVisible = true;\n\n /** @deprecated Use {@link FilterPanelVisible}. */\n public get filterPanelVisible() {\n return this.FilterPanelVisible;\n }\n /** @deprecated Use {@link FilterPanelVisible}. */\n public set filterPanelVisible(value) {\n this.FilterPanelVisible = value;\n }\n public SelectedEntity: EntityInfo | null = null;\n\n /** @deprecated Use {@link SelectedEntity}. */\n public get selectedEntity(): EntityInfo | null {\n return this.SelectedEntity;\n }\n /** @deprecated Use {@link SelectedEntity}. */\n public set selectedEntity(value: EntityInfo | null) {\n this.SelectedEntity = value;\n }\n public FilteredEntities: EntityInfo[] = [];\n\n /** @deprecated Use {@link FilteredEntities}. */\n public get filteredEntities(): EntityInfo[] {\n return this.FilteredEntities;\n }\n /** @deprecated Use {@link FilteredEntities}. */\n public set filteredEntities(value: EntityInfo[]) {\n this.FilteredEntities = value;\n }\n\n /** Total unfiltered entity count — feeds the chrome's X-of-Y badge. */\n public get TotalEntityCount(): number {\n return this.ErdComposite?.entities?.length ?? 0;\n }\n\n // State management\n private userStateChangeSubject = new Subject<ERDCompositeState>();\n private hasLoadedUserState = false;\n private metadata = this.ProviderToUse;\n private userSettingEntity: MJUserSettingEntity | null = null;\n\n ngAfterViewInit(): void {\n // Setup debounced state persistence to MJ: User Settings\n this.userStateChangeSubject.pipe(\n debounceTime(1000)\n ).subscribe(state => {\n this.saveStateToUserSettings(state);\n });\n\n // Publish the initial agent context and register the (read-only) client tools\n // the AI agent can invoke against this surface. Ongoing re-emit happens in\n // onStateChange (every ERD state change).\n this.publishAgentContext();\n this.registerAgentClientTools();\n }\n\n async GetResourceDisplayName(data: ResourceData): Promise<string> {\n return \"Entity Administration\"\n }\n\n override ngOnDestroy(): void {\n this.userStateChangeSubject.complete();\n super.ngOnDestroy();\n }\n\n protected initDashboard(): void {\n // Initialize dashboard - called by BaseDashboard\n }\n\n protected loadData(): void {\n // Data loading is handled by ERDCompositeComponent\n }\n\n public ToggleFilterPanel(): void {\n this.FilterPanelVisible = !this.FilterPanelVisible;\n if (this.ErdComposite) {\n this.ErdComposite.onToggleFilterPanel();\n }\n }\n\n /** @deprecated Use {@link ToggleFilterPanel}. */\n public toggleFilterPanel(): void {\n return this.ToggleFilterPanel();\n }\n\n public OnStateChange(state: ERDCompositeState): void {\n // Update local state to keep header controls in sync\n this.FilterPanelVisible = state.filterPanelVisible;\n this.FilteredEntities = this.ErdComposite?.filteredEntities || [];\n\n if (state.selectedEntityId && this.ErdComposite) {\n this.SelectedEntity = this.ErdComposite.entities.find(e => UUIDsEqual(e.ID, state.selectedEntityId)) || null;\n } else {\n this.SelectedEntity = null;\n }\n\n // Load user state when data becomes available for the first time\n if (this.ErdComposite?.isDataLoaded && !this.hasLoadedUserState) {\n this.hasLoadedUserState = true;\n this.loadStateFromUserSettings();\n }\n\n // Keep the AI agent's view of this surface in sync with every ERD state change\n // (entity selection, filter panel visibility, filtered count, etc.).\n this.publishAgentContext();\n }\n\n /** @deprecated Use {@link OnStateChange}. */\n public onStateChange(state: ERDCompositeState): void {\n return this.OnStateChange(state);\n }\n\n public OnUserStateChange(state: ERDCompositeState): void {\n // Queue state for debounced persistence\n this.userStateChangeSubject.next(state);\n }\n\n /** @deprecated Use {@link OnUserStateChange}. */\n public onUserStateChange(state: ERDCompositeState): void {\n return this.OnUserStateChange(state);\n }\n\n public OnEntityOpened(entity: EntityInfo): void {\n this.OpenEntity(entity);\n }\n\n /** @deprecated Use {@link OnEntityOpened}. */\n public onEntityOpened(entity: EntityInfo): void {\n return this.OnEntityOpened(entity);\n }\n\n /**\n * Opens a record the ERD asked for (the details panel's Open button sends the entity's own\n * `MJ: Entities` record). Navigates directly rather than emitting `OpenEntityRecord`: not every\n * Explorer host subscribes to that output, and where none does the button silently did nothing.\n */\n public OnOpenRecord(event: {EntityName: string, RecordID: string}): void {\n // The ERD emits records of arbitrary entities — resolve the key against the entity's metadata\n const recordKey = CompositeKey.FromURLSegment(this.metadata.EntityByName(event.EntityName), event.RecordID);\n this.navigationService.OpenEntityRecord(event.EntityName, recordKey);\n }\n\n /** @deprecated Use {@link OnOpenRecord}. */\n public onOpenRecord(event: {EntityName: string, RecordID: string}): void {\n return this.OnOpenRecord(event);\n }\n\n public OpenEntity(entity: EntityInfo): void {\n this.Interaction.emit({\n type: 'openEntity',\n entity: entity,\n data: { entityId: entity.ID, entityName: entity.Name }\n });\n }\n\n /** @deprecated Use {@link OpenEntity}. */\n public openEntity(entity: EntityInfo): void {\n return this.OpenEntity(entity);\n }\n\n /**\n * Load ERD state from MJ: User Settings entity using UserInfoEngine for cached access\n */\n private async loadStateFromUserSettings(): Promise<void> {\n try {\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) {\n this.NotifyLoadComplete();\n return;\n }\n\n const engine = UserInfoEngine.Instance;\n\n // Find setting from cached user settings\n const setting = engine.UserSettings.find(s => s.Setting === ERD_SETTINGS_KEY);\n\n if (setting) {\n this.userSettingEntity = setting;\n if (this.userSettingEntity.Value) {\n const savedState = JSON.parse(this.userSettingEntity.Value) as Partial<ERDCompositeState>;\n if (this.ErdComposite) {\n this.ErdComposite.loadUserState(savedState);\n }\n }\n }\n\n this.NotifyLoadComplete();\n } catch (error) {\n console.warn('Failed to load ERD state from User Settings:', error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Save ERD state to MJ: User Settings entity using UserInfoEngine for cached lookup\n */\n private async saveStateToUserSettings(state: ERDCompositeState): Promise<void> {\n try {\n const userId = this.metadata.CurrentUser?.ID;\n if (!userId) return;\n\n // Find existing setting from cached user settings if not already loaded\n if (!this.userSettingEntity) {\n const engine = UserInfoEngine.Instance;\n const setting = engine.UserSettings.find(s => s.Setting === ERD_SETTINGS_KEY);\n\n if (setting) {\n this.userSettingEntity = setting;\n } else {\n this.userSettingEntity = await this.metadata.GetEntityObject<MJUserSettingEntity>('MJ: User Settings');\n this.userSettingEntity.UserID = userId;\n this.userSettingEntity.Setting = ERD_SETTINGS_KEY;\n }\n }\n\n // Save the state as JSON\n this.userSettingEntity.Value = JSON.stringify(state);\n await this.userSettingEntity.Save();\n } catch (error) {\n console.warn('Failed to save ERD state to User Settings:', error);\n }\n }\n\n // ========================================\n // AI AGENT CONTEXT & CLIENT TOOLS\n //\n // 🔒 SAFETY BOUNDARY: The Entity Admin surface is a metadata BROWSER (the ERD\n // entity explorer). The context published and the tools registered below are\n // STRICTLY READ-ONLY / NAVIGATIONAL — select/clear an entity, toggle the filter\n // panel, refresh the diagram. NO tool here (and none added in future) may edit,\n // create, or delete entity metadata, alter schema, or perform any other mutation.\n // The agent browses; the user makes changes from the dedicated entity forms.\n // ========================================\n\n /**\n * Publish the current Entity Admin ERD-browser state to the AI agent via\n * NavigationService. Reports — at Data-Explorer depth — the entity counts (total +\n * filtered), the active filter values (schema / search / status), the current\n * selection (id + registered name + DISPLAY name + schema + description + field count\n * + a bounded relationship summary), filter-panel visibility, a bounded list of\n * available entity DISPLAY names, and the schema-grouping landscape. The shaping lives\n * in the pure {@link buildEntityAdminAgentContext} helper so it stays unit-testable.\n * Called on init (ngAfterViewInit) and on every ERD state change (onStateChange).\n */\n private publishAgentContext(): void {\n const filters = this.ErdComposite?.filters;\n const context = BuildEntityAdminAgentContext({\n TotalEntityCount: this.TotalEntityCount,\n FilteredEntityCount: this.FilteredEntities.length,\n SelectedEntityId: this.SelectedEntity?.ID ?? null,\n SelectedEntityName: this.SelectedEntity?.Name ?? null,\n SelectedEntityDisplayName: this.SelectedEntity\n ? EntityDisplayName(this.SelectedEntity.Name, this.SelectedEntity.DisplayName)\n : null,\n SelectedEntitySchema: this.SelectedEntity?.SchemaName ?? null,\n SelectedEntityDescription: this.SelectedEntity?.Description ?? null,\n SelectedEntityFieldCount: this.SelectedEntity?.Fields?.length ?? null,\n RelatedEntities: this.buildRelatedEntitySummaries(),\n FilterPanelVisible: this.FilterPanelVisible,\n SchemaFilter: filters?.schemaName ?? null,\n SearchText: filters?.entityName ?? '',\n StatusFilter: filters?.entityStatus ?? null,\n AvailableEntityNames: this.FilteredEntities.map(e => EntityDisplayName(e.Name, e.DisplayName)),\n SchemaGroups: this.buildSchemaGroupSummaries(),\n });\n this.navigationService.SetAgentContext(this, context);\n }\n\n /** Related-entity summaries for the selected entity (display name + relationship type). */\n private buildRelatedEntitySummaries(): RelatedEntitySummary[] {\n if (!this.SelectedEntity) {\n return [];\n }\n return this.SelectedEntity.RelatedEntities.map(re => ({\n Name: re.RelatedEntity ?? re.RelatedEntityID ?? '(unknown)',\n RelationshipType: re.Type ?? 'Related',\n }));\n }\n\n /** Schema-grouping summaries across the currently-visible (filtered) entities. */\n private buildSchemaGroupSummaries(): SchemaGroupSummary[] {\n const counts = new Map<string, number>();\n for (const e of this.FilteredEntities) {\n const schema = e.SchemaName || '(no schema)';\n counts.set(schema, (counts.get(schema) ?? 0) + 1);\n }\n return Array.from(counts.entries())\n .map(([SchemaName, EntityCount]) => ({ SchemaName, EntityCount }))\n .sort((a, b) => b.EntityCount - a.EntityCount);\n }\n\n /** The ERD's loaded entities, narrowed to the resolver's structural shape. */\n private get entityCandidates(): EntityNameCandidate[] {\n return this.ErdComposite?.entities ?? [];\n }\n\n /**\n * Register the (read-only / navigational) client tools the AI agent can invoke\n * against the Entity Admin ERD. Each handler delegates to the same component or\n * ERD method a user interaction would call, and returns `{ Success: true }` on\n * success or `{ Success: false, ErrorMessage }` on failure. Handlers never throw.\n *\n * Tools (browse only — see SAFETY BOUNDARY above):\n * - SelectEntity: select/focus an entity by its ID, registered Name, or DISPLAY name.\n * - ToggleFilterPanel: show/hide the ERD filter panel.\n * - ClearEntitySelection: deselect the currently selected entity.\n * - RefreshERD: re-render the ERD diagram.\n * - FilterEntities: narrow the ERD by schema and/or a name search.\n * - NavigateToEntityRecord: open an entity's record list/view (read-only navigation).\n */\n private registerAgentClientTools(): void {\n this.navigationService.SetAgentClientTools(this, [\n {\n Name: 'SelectEntity',\n Description: 'Select and focus an entity in the ERD by its ID, registered name, or the display name shown on screen (e.g. \"AI Models\" rather than \"MJ: AI Models\"). Read-only — opens nothing for edit.',\n ParameterSchema: { type: 'object', properties: { entity: { type: 'string', description: 'Entity ID, registered name, or display name.' } }, required: ['entity'] },\n Handler: async (params: Record<string, unknown>) => this.toolSelectEntity(params),\n },\n {\n Name: 'ToggleFilterPanel',\n Description: 'Show or hide the ERD entity filter panel.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => {\n this.ToggleFilterPanel();\n this.publishAgentContext();\n return { Success: true };\n },\n },\n {\n Name: 'ClearEntitySelection',\n Description: 'Clear the currently selected entity in the ERD.',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => this.toolClearEntitySelection(),\n },\n {\n Name: 'RefreshERD',\n Description: 'Re-render the ERD diagram (no data is modified).',\n ParameterSchema: { type: 'object', properties: {} },\n Handler: async () => this.toolRefreshERD(),\n },\n {\n Name: 'FilterEntities',\n Description: 'Narrow the entities shown in the ERD by schema name and/or an entity-name search. Pass an empty string for a field to clear it; pass neither to reset all filters. No data is modified.',\n ParameterSchema: {\n type: 'object',\n properties: {\n schema: { type: 'string', description: 'Schema name to filter by (empty string clears it).' },\n search: { type: 'string', description: 'Entity-name search text (empty string clears it).' },\n },\n },\n Handler: async (params: Record<string, unknown>) => this.toolFilterEntities(params),\n },\n {\n Name: 'NavigateToEntityRecord',\n Description: 'Open the record list / data view for an entity (by ID, registered name, or display name) for viewing. Read-only navigation — opens nothing for edit and creates no records.',\n ParameterSchema: { type: 'object', properties: { entity: { type: 'string', description: 'Entity ID, registered name, or display name.' } }, required: ['entity'] },\n Handler: async (params: Record<string, unknown>) => this.toolNavigateToEntityRecord(params),\n },\n ]);\n }\n\n /** Resolve an entity by ID / registered name / display name and select/focus it. */\n private toolSelectEntity(params: Record<string, unknown>): AgentToolResult {\n const validated = ValidateStringParam(params['entity'], 'entity');\n if (!validated.ok) {\n return validated.result;\n }\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n const candidates = this.entityCandidates;\n const match = ResolveEntityByIdOrName(validated.value, candidates);\n if (!match) {\n return { Success: false, ErrorMessage: BuildEntityNotFoundError(validated.value, candidates) };\n }\n const entity = this.ErdComposite.entities.find(e => UUIDsEqual(e.ID, match.ID));\n if (!entity) {\n return { Success: false, ErrorMessage: BuildEntityNotFoundError(validated.value, candidates) };\n }\n this.ErdComposite.onEntitySelected(entity);\n return { Success: true, ErrorMessage: undefined };\n }\n\n /** Deselect the currently selected entity in the ERD. */\n private toolClearEntitySelection(): AgentToolResult {\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n this.ErdComposite.onEntityDeselected();\n return { Success: true };\n }\n\n /** Re-render the ERD diagram. */\n private toolRefreshERD(): AgentToolResult {\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n this.ErdComposite.refreshERD();\n return { Success: true };\n }\n\n /** Narrow the ERD by schema and/or a name search (read-only — only changes what's shown). */\n private toolFilterEntities(params: Record<string, unknown>): AgentToolResult {\n if (!this.ErdComposite) {\n return { Success: false, ErrorMessage: 'The ERD is not ready yet.' };\n }\n const schemaRaw = params['schema'];\n const searchRaw = params['search'];\n\n // Neither provided → reset all filters.\n if (schemaRaw === undefined && searchRaw === undefined) {\n this.ErdComposite.onResetFilters();\n this.publishAgentContext();\n return { Success: true };\n }\n\n const next = { ...this.ErdComposite.filters };\n if (schemaRaw !== undefined) {\n const validated = ValidateStringParam(schemaRaw, 'schema');\n if (!validated.ok) {\n return validated.result;\n }\n const schema = validated.value.trim();\n // Tolerant case-insensitive match against the known schemas; empty clears it.\n if (!schema) {\n next.schemaName = null;\n } else {\n const known = Array.from(new Set(this.entityCandidates.map(e => (e as { SchemaName?: string }).SchemaName).filter((s): s is string => !!s)));\n const resolved = known.find(s => s.toLowerCase() === schema.toLowerCase());\n if (!resolved) {\n return { Success: false, ErrorMessage: `No schema matches \"${schema}\". Available schemas: ${known.join(', ') || '(none)'}.` };\n }\n next.schemaName = resolved;\n }\n }\n if (searchRaw !== undefined) {\n const validated = ValidateStringParam(searchRaw, 'search');\n if (!validated.ok) {\n return validated.result;\n }\n next.entityName = validated.value;\n }\n\n this.ErdComposite.onFiltersChange(next);\n this.publishAgentContext();\n return { Success: true };\n }\n\n /** Open an entity's record list / data view for viewing (read-only navigation). */\n private toolNavigateToEntityRecord(params: Record<string, unknown>): AgentToolResult {\n const validated = ValidateStringParam(params['entity'], 'entity');\n if (!validated.ok) {\n return validated.result;\n }\n const candidates = this.entityCandidates;\n const match = ResolveEntityByIdOrName(validated.value, candidates);\n if (!match) {\n return { Success: false, ErrorMessage: BuildEntityNotFoundError(validated.value, candidates) };\n }\n // Open the entity's dynamic record view (read-only navigation — no edit, no create).\n this.navigationService.OpenDynamicView(match.Name);\n return { Success: true };\n }\n}\n","<mj-page-layout>\n <mj-page-header\n Title=\"Entity Admin\"\n Icon=\"fa-solid fa-database\"\n Subtitle=\"Browse and configure entity definitions\">\n <!-- X-of-Y filtered count earns its meta spot per chrome conventions §2. -->\n <div meta>\n <mj-stat-badge\n [Count]=\"filteredEntities.length\"\n [Total]=\"TotalEntityCount\"\n Label=\"entities\">\n </mj-stat-badge>\n </div>\n <div actions>\n <button\n mjButton\n [variant]=\"filterPanelVisible ? 'primary' : 'secondary'\"\n size=\"sm\"\n (click)=\"toggleFilterPanel()\"\n title=\"Toggle Filters\">\n <i class=\"fa-solid fa-filter\"></i> <span class=\"action-btn-label\">Filters</span>\n </button>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\">\n @if (isLoading) {\n <div class=\"loading-container\">\n <mj-loading [text]=\"loadingMessage\" size=\"large\"></mj-loading>\n </div>\n }\n\n @if (error) {\n <div class=\"error-container\">\n <p class=\"error-message\">{{ error }}</p>\n </div>\n }\n\n @if (!isLoading && !error) {\n <div class=\"dashboard-content\">\n <mj-erd-composite\n #erdComposite\n [isRefreshingERD]=\"isRefreshingERD\"\n (stateChange)=\"onStateChange($event)\"\n (userStateChange)=\"onUserStateChange($event)\"\n (entityOpened)=\"onEntityOpened($event)\"\n (openRecord)=\"onOpenRecord($event)\"\n >\n </mj-erd-composite>\n </div>\n }\n </mj-page-body>\n</mj-page-layout>"]}
|
|
@@ -739,7 +739,7 @@ let OverviewComponent = class OverviewComponent extends BaseResourceComponent {
|
|
|
739
739
|
i0.ɵɵconditional(ctx.Notification ? 7 : -1);
|
|
740
740
|
i0.ɵɵadvance();
|
|
741
741
|
i0.ɵɵconditional(ctx.IsLoading ? 8 : ctx.Summaries.length === 0 ? 9 : 10);
|
|
742
|
-
} }, dependencies: [i1.MJPageLayoutComponent, i1.MJPageHeaderComponent, i1.MJPageBodyComponent, i1.MJStatBadgeComponent, i1.MJRefreshButtonComponent, i1.MJEmptyStateComponent, i2.LoadingComponent, i3.DecimalPipe], styles: ["\n\n\n.overview[_ngcontent-%COMP%] {\n \n\n max-width: 1440px;\n margin: 0 auto;\n}\n\n\n\n\n\n\n.notification[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n font-weight: 500;\n animation: _ngcontent-%COMP%_slideDown 0.25s ease-out;\n}\n.notification[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:first-child { font-size: 16px; }\n.notification-message[_ngcontent-%COMP%] { flex: 1; }\n.notification-success[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-color-success-200);\n}\n.notification-error[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-color-error-200);\n}\n.notification-dismiss[_ngcontent-%COMP%] {\n background: none;\n border: none;\n cursor: pointer;\n color: inherit;\n opacity: 0.5;\n font-size: 14px;\n padding: 4px;\n line-height: 1;\n}\n.notification-dismiss[_ngcontent-%COMP%]:hover { opacity: 1; }\n\n@keyframes _ngcontent-%COMP%_slideDown {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n\n\n\n\n\n\n\n.metric-strip[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 16px;\n margin-bottom: 32px;\n}\n\n.metric-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 18px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n}\n.metric-card[_ngcontent-%COMP%]:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n transform: translateY(-1px);\n}\n\n.metric-icon[_ngcontent-%COMP%] {\n position: relative;\n width: 44px;\n height: 44px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n}\n.metric-icon-blue[_ngcontent-%COMP%] { background: var(--mj-status-info-bg); color: var(--mj-brand-primary); }\n.metric-icon-green[_ngcontent-%COMP%] { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-emerald[_ngcontent-%COMP%] { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-orange[_ngcontent-%COMP%] { background: var(--mj-color-warning-50); color: var(--mj-color-warning-600); }\n\n.metric-body[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.metric-value[_ngcontent-%COMP%] {\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--mj-text-primary);\n}\n.metric-value-indigo[_ngcontent-%COMP%] { color: var(--mj-color-indigo-500); }\n\n.metric-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n.metric-ring[_ngcontent-%COMP%] {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.metric-ring-inner[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.metric-ring-value[_ngcontent-%COMP%] {\n font-size: 8px;\n font-weight: 700;\n color: var(--mj-color-indigo-500);\n}\n\n\n.pulse-dot[_ngcontent-%COMP%] {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--mj-color-success-600);\n animation: _ngcontent-%COMP%_pulse 1.5s ease-in-out infinite;\n}\n\n@keyframes _ngcontent-%COMP%_pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.4); }\n}\n\n\n\n\n.section-heading[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.section-heading[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.section-heading[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n.refresh-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n \n flex-shrink: 0;\n box-sizing: border-box;\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n.refresh-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-color-info-200);\n}\n\n.pipeline-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 20px;\n margin-bottom: 32px;\n}\n\n.pipeline-card[_ngcontent-%COMP%] {\n position: relative;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n \n\n\n overflow: hidden;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.pipeline-card[_ngcontent-%COMP%]:hover {\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n}\n\n.pipeline-card-error[_ngcontent-%COMP%] {\n border-left: 3px solid var(--mj-status-error);\n}\n\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.card-header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n.card-icon-wrap[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n}\n.card-title-wrap[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.card-title[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.card-subtitle[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n\n.status-dot[_ngcontent-%COMP%] {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-top: 4px;\n position: relative;\n}\n.status-dot-green[_ngcontent-%COMP%] { background: var(--mj-color-success-600); }\n.status-dot-amber[_ngcontent-%COMP%] { background: var(--mj-status-warning); }\n.status-dot-red[_ngcontent-%COMP%] { background: var(--mj-status-error); }\n.status-dot-gray[_ngcontent-%COMP%] { background: var(--mj-color-neutral-300); }\n\n.dot-pulse[_ngcontent-%COMP%] {\n position: absolute;\n inset: -3px;\n border-radius: 50%;\n border: 2px solid var(--mj-status-warning);\n animation: _ngcontent-%COMP%_dotPulse 1.5s ease-in-out infinite;\n}\n@keyframes _ngcontent-%COMP%_dotPulse {\n 0%, 100% { opacity: 0.3; transform: scale(1); }\n 50% { opacity: 0; transform: scale(1.6); }\n}\n\n\n.pipeline-flow[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 8px 0;\n}\n.flow-node[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n flex-shrink: 0;\n}\n.flow-node-mj[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n}\n.flow-arrow[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 4px;\n}\n.flow-line[_ngcontent-%COMP%] {\n flex: 1;\n height: 2px;\n background: var(--mj-border-default);\n}\n.flow-count[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n padding: 2px 8px;\n background: var(--mj-bg-page);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n font-weight: 500;\n}\n\n\n.card-stats[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n}\n.stat-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n}\n.stat-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n.stat-value[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.stat-error[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n\n.sparkline-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.sparkline-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n white-space: nowrap;\n}\n.sparkline-dots[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.spark-dot[_ngcontent-%COMP%] {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n display: inline-block;\n}\n.spark-green[_ngcontent-%COMP%] { background: var(--mj-color-success-600); }\n.spark-red[_ngcontent-%COMP%] { background: var(--mj-status-error); }\n.spark-amber[_ngcontent-%COMP%] { background: var(--mj-status-warning); }\n.spark-gray[_ngcontent-%COMP%] { background: var(--mj-color-neutral-300); }\n.sparkline-none[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-color-neutral-300);\n font-style: italic;\n}\n\n\n.card-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-end;\n \n\n min-width: 0;\n padding-top: 4px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.btn-sync[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n \n max-width: 100%;\n box-sizing: border-box;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n}\n.btn-sync[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-color-info-200);\n}\n.btn-sync[_ngcontent-%COMP%]:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.btn-sync-disabled[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n border-color: var(--mj-border-default);\n}\n\n\n\n\n.bottom-split[_ngcontent-%COMP%] {\n display: flex;\n gap: 20px;\n}\n\n.panel-title[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.panel-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n\n.chart-panel[_ngcontent-%COMP%] {\n flex: 6;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.bar-chart[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 12px;\n height: 200px;\n padding-top: 24px;\n}\n.bar-column[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n position: relative;\n}\n.bar-count[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 600;\n color: var(--mj-text-muted);\n margin-bottom: 4px;\n}\n.bar-fill[_ngcontent-%COMP%] {\n width: 100%;\n max-width: 40px;\n background: linear-gradient(180deg, var(--mj-brand-primary) 0%, var(--mj-color-accent-400) 100%);\n border-radius: 4px 4px 0 0;\n min-height: 2px;\n transition: height 0.3s ease;\n}\n.bar-day[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n margin-top: 6px;\n text-align: center;\n}\n\n\n.feed-panel[_ngcontent-%COMP%] {\n flex: 4;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n max-height: 340px;\n overflow-y: auto;\n}\n\n.feed-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.feed-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border-radius: 6px;\n transition: background 0.15s;\n}\n.feed-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-page);\n}\n\n.feed-icon-green[_ngcontent-%COMP%] { color: var(--mj-color-success-600); }\n.feed-icon-red[_ngcontent-%COMP%] { color: var(--mj-color-error-600); }\n.feed-icon-amber[_ngcontent-%COMP%] { color: var(--mj-color-warning-600); }\n\n.feed-details[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.feed-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.feed-meta[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n.feed-badge[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.feed-badge-green[_ngcontent-%COMP%] { background: var(--mj-status-success-bg); color: var(--mj-color-success-800); }\n.feed-badge-amber[_ngcontent-%COMP%] { background: var(--mj-color-warning-50); color: var(--mj-color-warning-800); }\n.feed-badge-red[_ngcontent-%COMP%] { background: var(--mj-status-error-bg); color: var(--mj-status-error); }\n\n.empty-hint[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n font-size: 13px;\n font-style: italic;\n}\n\n\n\n\n@media (max-width: 1200px) {\n .pipeline-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n .metric-strip[_ngcontent-%COMP%] {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n@media (max-width: 900px) {\n .pipeline-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n .metric-strip[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n .bottom-split[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n .card-stats[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 600px) {\n .overview[_ngcontent-%COMP%] {\n padding: 16px;\n }\n .metric-strip[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n}"] });
|
|
742
|
+
} }, dependencies: [i1.MJPageLayoutComponent, i1.MJPageHeaderComponent, i1.MJPageBodyComponent, i1.MJStatBadgeComponent, i1.MJRefreshButtonComponent, i1.MJEmptyStateComponent, i2.LoadingComponent, i3.DecimalPipe], styles: ["\n\n\n.overview[_ngcontent-%COMP%] {\n \n\n max-width: 1440px;\n margin: 0 auto;\n}\n\n\n\n\n\n\n.notification[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n font-weight: 500;\n animation: _ngcontent-%COMP%_slideDown 0.25s ease-out;\n}\n.notification[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:first-child { font-size: 16px; }\n.notification-message[_ngcontent-%COMP%] { flex: 1; }\n.notification-success[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-color-success-200);\n}\n.notification-error[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-color-error-200);\n}\n.notification-dismiss[_ngcontent-%COMP%] {\n background: none;\n border: none;\n cursor: pointer;\n color: inherit;\n opacity: 0.5;\n font-size: 14px;\n padding: 4px;\n line-height: 1;\n}\n.notification-dismiss[_ngcontent-%COMP%]:hover { opacity: 1; }\n\n@keyframes _ngcontent-%COMP%_slideDown {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n\n\n\n\n\n\n\n.metric-strip[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 16px;\n margin-bottom: 32px;\n}\n\n.metric-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 18px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n}\n.metric-card[_ngcontent-%COMP%]:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n transform: translateY(-1px);\n}\n\n.metric-icon[_ngcontent-%COMP%] {\n position: relative;\n width: 44px;\n height: 44px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n}\n.metric-icon-blue[_ngcontent-%COMP%] { background: var(--mj-status-info-bg); color: var(--mj-brand-primary); }\n.metric-icon-green[_ngcontent-%COMP%] { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-emerald[_ngcontent-%COMP%] { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-orange[_ngcontent-%COMP%] { background: var(--mj-color-warning-50); color: var(--mj-color-warning-600); }\n\n.metric-body[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.metric-value[_ngcontent-%COMP%] {\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--mj-text-primary);\n}\n.metric-value-indigo[_ngcontent-%COMP%] { color: var(--mj-color-indigo-500); }\n\n.metric-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n.metric-ring[_ngcontent-%COMP%] {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.metric-ring-inner[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.metric-ring-value[_ngcontent-%COMP%] {\n font-size: 8px;\n font-weight: 700;\n color: var(--mj-color-indigo-500);\n}\n\n\n.pulse-dot[_ngcontent-%COMP%] {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--mj-color-success-600);\n animation: _ngcontent-%COMP%_pulse 1.5s ease-in-out infinite;\n}\n\n@keyframes _ngcontent-%COMP%_pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.4); }\n}\n\n\n\n\n.section-heading[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.section-heading[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.section-heading[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n.refresh-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n \n flex-shrink: 0;\n box-sizing: border-box;\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n.refresh-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-status-info-border);\n}\n\n.pipeline-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 20px;\n margin-bottom: 32px;\n}\n\n.pipeline-card[_ngcontent-%COMP%] {\n position: relative;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n \n\n\n overflow: hidden;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.pipeline-card[_ngcontent-%COMP%]:hover {\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n}\n\n.pipeline-card-error[_ngcontent-%COMP%] {\n border-left: 3px solid var(--mj-status-error);\n}\n\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.card-header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n.card-icon-wrap[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n}\n.card-title-wrap[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.card-title[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.card-subtitle[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n\n.status-dot[_ngcontent-%COMP%] {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-top: 4px;\n position: relative;\n}\n.status-dot-green[_ngcontent-%COMP%] { background: var(--mj-color-success-600); }\n.status-dot-amber[_ngcontent-%COMP%] { background: var(--mj-status-warning); }\n.status-dot-red[_ngcontent-%COMP%] { background: var(--mj-status-error); }\n.status-dot-gray[_ngcontent-%COMP%] { background: var(--mj-color-neutral-300); }\n\n.dot-pulse[_ngcontent-%COMP%] {\n position: absolute;\n inset: -3px;\n border-radius: 50%;\n border: 2px solid var(--mj-status-warning);\n animation: _ngcontent-%COMP%_dotPulse 1.5s ease-in-out infinite;\n}\n@keyframes _ngcontent-%COMP%_dotPulse {\n 0%, 100% { opacity: 0.3; transform: scale(1); }\n 50% { opacity: 0; transform: scale(1.6); }\n}\n\n\n.pipeline-flow[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 8px 0;\n}\n.flow-node[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n flex-shrink: 0;\n}\n.flow-node-mj[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n}\n.flow-arrow[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 4px;\n}\n.flow-line[_ngcontent-%COMP%] {\n flex: 1;\n height: 2px;\n background: var(--mj-border-default);\n}\n.flow-count[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n padding: 2px 8px;\n background: var(--mj-bg-page);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n font-weight: 500;\n}\n\n\n.card-stats[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n}\n.stat-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n}\n.stat-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n.stat-value[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.stat-error[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n\n.sparkline-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.sparkline-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n white-space: nowrap;\n}\n.sparkline-dots[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.spark-dot[_ngcontent-%COMP%] {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n display: inline-block;\n}\n.spark-green[_ngcontent-%COMP%] { background: var(--mj-color-success-600); }\n.spark-red[_ngcontent-%COMP%] { background: var(--mj-status-error); }\n.spark-amber[_ngcontent-%COMP%] { background: var(--mj-status-warning); }\n.spark-gray[_ngcontent-%COMP%] { background: var(--mj-color-neutral-300); }\n.sparkline-none[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-color-neutral-300);\n font-style: italic;\n}\n\n\n.card-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-end;\n \n\n min-width: 0;\n padding-top: 4px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.btn-sync[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n \n max-width: 100%;\n box-sizing: border-box;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n}\n.btn-sync[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-status-info-border);\n}\n.btn-sync[_ngcontent-%COMP%]:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.btn-sync-disabled[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n border-color: var(--mj-border-default);\n}\n\n\n\n\n.bottom-split[_ngcontent-%COMP%] {\n display: flex;\n gap: 20px;\n}\n\n.panel-title[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.panel-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n\n.chart-panel[_ngcontent-%COMP%] {\n flex: 6;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.bar-chart[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 12px;\n height: 200px;\n padding-top: 24px;\n}\n.bar-column[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n position: relative;\n}\n.bar-count[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 600;\n color: var(--mj-text-muted);\n margin-bottom: 4px;\n}\n.bar-fill[_ngcontent-%COMP%] {\n width: 100%;\n max-width: 40px;\n background: linear-gradient(180deg, var(--mj-brand-primary) 0%, var(--mj-color-accent-400) 100%);\n border-radius: 4px 4px 0 0;\n min-height: 2px;\n transition: height 0.3s ease;\n}\n.bar-day[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n margin-top: 6px;\n text-align: center;\n}\n\n\n.feed-panel[_ngcontent-%COMP%] {\n flex: 4;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n max-height: 340px;\n overflow-y: auto;\n}\n\n.feed-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.feed-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border-radius: 6px;\n transition: background 0.15s;\n}\n.feed-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-page);\n}\n\n.feed-icon-green[_ngcontent-%COMP%] { color: var(--mj-color-success-600); }\n.feed-icon-red[_ngcontent-%COMP%] { color: var(--mj-color-error-600); }\n.feed-icon-amber[_ngcontent-%COMP%] { color: var(--mj-color-warning-600); }\n\n.feed-details[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.feed-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.feed-meta[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n.feed-badge[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.feed-badge-green[_ngcontent-%COMP%] { background: var(--mj-status-success-bg); color: var(--mj-color-success-800); }\n.feed-badge-amber[_ngcontent-%COMP%] { background: var(--mj-color-warning-50); color: var(--mj-color-warning-800); }\n.feed-badge-red[_ngcontent-%COMP%] { background: var(--mj-status-error-bg); color: var(--mj-status-error); }\n\n.empty-hint[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n font-size: 13px;\n font-style: italic;\n}\n\n\n\n\n@media (max-width: 1200px) {\n .pipeline-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n .metric-strip[_ngcontent-%COMP%] {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n@media (max-width: 900px) {\n .pipeline-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n .metric-strip[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n .bottom-split[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n .card-stats[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 600px) {\n .overview[_ngcontent-%COMP%] {\n padding: 16px;\n }\n .metric-strip[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n}"] });
|
|
743
743
|
};
|
|
744
744
|
OverviewComponent = __decorate([
|
|
745
745
|
RegisterClass(BaseResourceComponent, 'IntegrationOverview')
|
|
@@ -747,7 +747,7 @@ OverviewComponent = __decorate([
|
|
|
747
747
|
export { OverviewComponent };
|
|
748
748
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(OverviewComponent, [{
|
|
749
749
|
type: Component,
|
|
750
|
-
args: [{ standalone: false, selector: 'app-integration-overview', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Integration Overview\"\n Icon=\"fa-solid fa-gauge-high\"\n Subtitle=\"Health, throughput, and activity across all integrations\">\n @if (Summaries.length > 0) {\n <div meta>\n <mj-stat-badge [Count]=\"KPIs.TotalIntegrations\" Label=\"integrations\"></mj-stat-badge>\n </div>\n }\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\"></mj-refresh-button>\n </div>\n </mj-page-header>\n\n <mj-page-body>\n<div class=\"overview\">\n <!-- Notification Banner -->\n @if (Notification) {\n <div class=\"notification\" [class.notification-success]=\"Notification.Type === 'success'\"\n [class.notification-error]=\"Notification.Type === 'error'\">\n @if (Notification.Type === 'success') {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <i class=\"fa-solid fa-circle-xmark\"></i>\n }\n <span class=\"notification-message\">{{ Notification.Message }}</span>\n <button class=\"notification-dismiss\" (click)=\"DismissNotification()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n\n @if (IsLoading) {\n <mj-loading text=\"Loading integrations...\" size=\"medium\"></mj-loading>\n } @else if (Summaries.length === 0) {\n <mj-empty-state Icon=\"fa-solid fa-plug-circle-xmark\"\n Title=\"No Integrations Configured\"\n Message=\"Set up your first integration using the Connection Studio to start syncing data.\" />\n } @else {\n <!-- ====================================================================\n SECTION 1: Metric Strip\n ==================================================================== -->\n <div class=\"metric-strip\">\n <!-- Total Pipelines -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-blue\">\n <i class=\"fa-solid fa-diagram-project\"></i>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ KPIs.TotalIntegrations }}</span>\n <span class=\"metric-label\">Total Pipelines</span>\n </div>\n </div>\n\n <!-- Records Today -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-green\">\n <i class=\"fa-solid fa-database\"></i>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ FormatNumber(KPIs.RecordsSyncedToday) }}</span>\n <span class=\"metric-label\">Records Today</span>\n </div>\n </div>\n\n <!-- Success Rate (with conic gradient ring) -->\n <div class=\"metric-card\">\n <div class=\"metric-ring\" [style.background]=\"SuccessRateGradient\">\n <div class=\"metric-ring-inner\">\n <span class=\"metric-ring-value\">{{ SuccessRate }}%</span>\n </div>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value metric-value-indigo\">{{ SuccessRate }}%</span>\n <span class=\"metric-label\">Success Rate</span>\n </div>\n </div>\n\n <!-- Active Now -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-emerald\">\n <i class=\"fa-solid fa-bolt\"></i>\n @if (ActiveCount > 0) {\n <span class=\"pulse-dot\"></span>\n }\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ ActiveCount }}</span>\n <span class=\"metric-label\">Active Now</span>\n </div>\n </div>\n\n <!-- Avg Duration -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-orange\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ FormatDuration(KPIs.AverageSyncDurationMs) }}</span>\n <span class=\"metric-label\">Avg Duration</span>\n </div>\n </div>\n </div>\n\n <!-- ====================================================================\n SECTION 2: Pipeline Health Grid\n ==================================================================== -->\n <div class=\"section-heading\">\n <h3><i class=\"fa-solid fa-heart-pulse\"></i> Pipeline Health</h3>\n <button class=\"refresh-btn\" (click)=\"Refresh()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Refresh\n </button>\n </div>\n\n <div class=\"pipeline-grid\">\n @for (summary of Summaries; track summary.Integration.ID) {\n <div [class]=\"GetCardBorderClass(summary.StatusColor)\">\n <!-- Card Header -->\n <div class=\"card-header\">\n <div class=\"card-header-left\">\n <div class=\"card-icon-wrap\">\n <i [class]=\"GetIntegrationIcon(summary)\"></i>\n </div>\n <div class=\"card-title-wrap\">\n <span class=\"card-title\">{{ summary.Integration.Name }}</span>\n <span class=\"card-subtitle\">{{ summary.SourceType?.Name ?? 'Custom' }}</span>\n </div>\n </div>\n <div [class]=\"GetStatusDotClass(summary.StatusColor)\"\n [title]=\"GetStatusLabel(summary.StatusColor)\">\n @if (summary.LatestRun?.Status === 'In Progress') {\n <span class=\"dot-pulse\"></span>\n }\n </div>\n </div>\n\n <!-- Pipeline Flow Visual -->\n <div class=\"pipeline-flow\">\n <div class=\"flow-node\">\n <i [class]=\"GetIntegrationIcon(summary)\"></i>\n </div>\n <div class=\"flow-arrow\">\n <div class=\"flow-line\"></div>\n <span class=\"flow-count\">{{ GetEntityMapCount(summary.Integration.ID) }} maps</span>\n <div class=\"flow-line\"></div>\n </div>\n <div class=\"flow-node flow-node-mj\">\n <i class=\"fa-solid fa-layer-group\"></i>\n </div>\n </div>\n\n <!-- Stats Row -->\n <div class=\"card-stats\">\n <div class=\"stat-item\">\n <span class=\"stat-label\">Last sync</span>\n <span class=\"stat-value\">{{ summary.RelativeTime }}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Records</span>\n <span class=\"stat-value\">{{ FormatNumber(summary.LatestRun?.TotalRecords ?? 0) }}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Duration</span>\n <span class=\"stat-value\">{{ FormatDuration(summary.DurationMs) }}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Errors</span>\n <span class=\"stat-value\" [class.stat-error]=\"summary.TotalErrors > 0\">\n {{ summary.TotalErrors }}\n </span>\n </div>\n </div>\n\n <!-- Sparkline (last 5 runs) -->\n <div class=\"sparkline-row\">\n <span class=\"sparkline-label\">Recent runs</span>\n <div class=\"sparkline-dots\">\n @for (run of summary.RecentRuns; track run.ID) {\n <span [class]=\"GetSparklineDotClass(run)\"\n [title]=\"GetSparklineTooltip(run)\"></span>\n }\n @if (summary.RecentRuns.length === 0) {\n <span class=\"sparkline-none\">No runs</span>\n }\n </div>\n </div>\n\n <!-- Card Footer -->\n <div class=\"card-footer\">\n @if (IsRunning(summary.Integration.ID)) {\n <button class=\"btn-sync btn-sync-disabled\" disabled>\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running...\n </button>\n } @else {\n <button class=\"btn-sync\"\n (click)=\"RunSync(summary.Integration.ID)\"\n [disabled]=\"!summary.Integration.IsActive || RunningIntegrationID !== null\">\n <i class=\"fa-solid fa-play\"></i> Sync Now\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- ====================================================================\n SECTION 3: Bottom - Bar Chart & Activity Feed\n ==================================================================== -->\n <div class=\"bottom-split\">\n <!-- Bar Chart (left, 60%) -->\n <div class=\"chart-panel\">\n <h3 class=\"panel-title\"><i class=\"fa-solid fa-chart-bar\"></i> Records Synced (7 days)</h3>\n @if (DailyCounts.length === 0) {\n <p class=\"empty-hint\">No data available.</p>\n } @else {\n <div class=\"bar-chart\">\n @for (day of DailyCounts; track day.Date) {\n <div class=\"bar-column\">\n <div class=\"bar-count\">{{ FormatNumber(day.Records) }}</div>\n <div class=\"bar-fill\" [style.height.%]=\"BarHeight(day.Records)\"></div>\n <div class=\"bar-day\">{{ day.Label }}</div>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Activity Feed (right, 40%) -->\n <div class=\"feed-panel\">\n <h3 class=\"panel-title\"><i class=\"fa-solid fa-stream\"></i> Recent Activity</h3>\n @if (ActivityFeed.length === 0) {\n <p class=\"empty-hint\">No recent activity.</p>\n } @else {\n <div class=\"feed-list\">\n @for (item of ActivityFeed; track item.RunID) {\n <div class=\"feed-item\">\n <i [class]=\"ActivityStatusIcon(item.Status)\"\n [class.feed-icon-green]=\"item.StatusColor === 'green'\"\n [class.feed-icon-red]=\"item.StatusColor === 'red'\"\n [class.feed-icon-amber]=\"item.StatusColor === 'amber'\">\n </i>\n <div class=\"feed-details\">\n <span class=\"feed-name\">{{ item.IntegrationName }}</span>\n <span class=\"feed-meta\">{{ item.TotalRecords | number }} records · {{ item.RelativeTime }}</span>\n </div>\n <span class=\"feed-badge\" [class]=\"'feed-badge-' + item.StatusColor\">\n {{ item.Status }}\n </span>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n</div>\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/* ==========================================================================\n Integration Overview - Root\n ========================================================================== */\n.overview {\n /* padding + flex/scroll now owned by <mj-page-body>; keep max-width for\n comfortable reading on ultra-wide displays. */\n max-width: 1440px;\n margin: 0 auto;\n}\n\n/* Slot passthrough now in <mj-page-header>; stat badges now via <mj-stat-badge>. */\n\n/* ==========================================================================\n Notification Banner\n ========================================================================== */\n.notification {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n font-weight: 500;\n animation: slideDown 0.25s ease-out;\n}\n.notification i:first-child { font-size: 16px; }\n.notification-message { flex: 1; }\n.notification-success {\n background: var(--mj-status-success-bg);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-color-success-200);\n}\n.notification-error {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-color-error-200);\n}\n.notification-dismiss {\n background: none;\n border: none;\n cursor: pointer;\n color: inherit;\n opacity: 0.5;\n font-size: 14px;\n padding: 4px;\n line-height: 1;\n}\n.notification-dismiss:hover { opacity: 1; }\n\n@keyframes slideDown {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ==========================================================================\n Empty State\n ========================================================================== */\n\n/* ==========================================================================\n SECTION 1: Metric Strip\n ========================================================================== */\n.metric-strip {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 16px;\n margin-bottom: 32px;\n}\n\n.metric-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 18px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n}\n.metric-card:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n transform: translateY(-1px);\n}\n\n.metric-icon {\n position: relative;\n width: 44px;\n height: 44px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n}\n.metric-icon-blue { background: var(--mj-status-info-bg); color: var(--mj-brand-primary); }\n.metric-icon-green { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-emerald { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-orange { background: var(--mj-color-warning-50); color: var(--mj-color-warning-600); }\n\n.metric-body {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.metric-value {\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--mj-text-primary);\n}\n.metric-value-indigo { color: var(--mj-color-indigo-500); }\n\n.metric-label {\n font-size: 11px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Conic gradient ring for success rate */\n.metric-ring {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.metric-ring-inner {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.metric-ring-value {\n font-size: 8px;\n font-weight: 700;\n color: var(--mj-color-indigo-500);\n}\n\n/* Pulse dot for active syncs */\n.pulse-dot {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--mj-color-success-600);\n animation: pulse 1.5s ease-in-out infinite;\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.4); }\n}\n\n/* ==========================================================================\n SECTION 2: Pipeline Health Grid\n ========================================================================== */\n.section-heading {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.section-heading h3 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.section-heading h3 i {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n.refresh-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Don't let the button shrink/overflow the heading row on narrow widths. */\n flex-shrink: 0;\n box-sizing: border-box;\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n.refresh-btn:hover {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-color-info-200);\n}\n\n.pipeline-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 20px;\n margin-bottom: 32px;\n}\n\n.pipeline-card {\n position: relative;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n /* Clip children to the card's rounded border so footer buttons (.btn-sync)\n and any flush-edge content can't poke past the rounded corners. box-shadow\n is drawn outside the border-box, so the hover shadow below is unaffected. */\n overflow: hidden;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.pipeline-card:hover {\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n}\n\n.pipeline-card-error {\n border-left: 3px solid var(--mj-status-error);\n}\n\n/* Card Header */\n.card-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.card-header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n.card-icon-wrap {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n}\n.card-title-wrap {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.card-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.card-subtitle {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n/* Status Dot */\n.status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-top: 4px;\n position: relative;\n}\n.status-dot-green { background: var(--mj-color-success-600); }\n.status-dot-amber { background: var(--mj-status-warning); }\n.status-dot-red { background: var(--mj-status-error); }\n.status-dot-gray { background: var(--mj-color-neutral-300); }\n\n.dot-pulse {\n position: absolute;\n inset: -3px;\n border-radius: 50%;\n border: 2px solid var(--mj-status-warning);\n animation: dotPulse 1.5s ease-in-out infinite;\n}\n@keyframes dotPulse {\n 0%, 100% { opacity: 0.3; transform: scale(1); }\n 50% { opacity: 0; transform: scale(1.6); }\n}\n\n/* Pipeline Flow Visual */\n.pipeline-flow {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 8px 0;\n}\n.flow-node {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n flex-shrink: 0;\n}\n.flow-node-mj {\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n}\n.flow-arrow {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 4px;\n}\n.flow-line {\n flex: 1;\n height: 2px;\n background: var(--mj-border-default);\n}\n.flow-count {\n font-size: 10px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n padding: 2px 8px;\n background: var(--mj-bg-page);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n font-weight: 500;\n}\n\n/* Card Stats */\n.card-stats {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n}\n.stat-item {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n}\n.stat-label {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n.stat-value {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.stat-error {\n color: var(--mj-status-error);\n}\n\n/* Sparkline Row */\n.sparkline-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.sparkline-label {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n white-space: nowrap;\n}\n.sparkline-dots {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.spark-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n display: inline-block;\n}\n.spark-green { background: var(--mj-color-success-600); }\n.spark-red { background: var(--mj-status-error); }\n.spark-amber { background: var(--mj-status-warning); }\n.spark-gray { background: var(--mj-color-neutral-300); }\n.sparkline-none {\n font-size: 10px;\n color: var(--mj-color-neutral-300);\n font-style: italic;\n}\n\n/* Card Footer */\n.card-footer {\n display: flex;\n justify-content: flex-end;\n /* min-width:0 lets the footer shrink so its button is bounded by the card's\n content box and can't be pushed past the right/bottom padding edge. */\n min-width: 0;\n padding-top: 4px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.btn-sync {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Keep the button bounded by its container so it stays inside the card. */\n max-width: 100%;\n box-sizing: border-box;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n}\n.btn-sync:hover:not(:disabled) {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-color-info-200);\n}\n.btn-sync:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.btn-sync-disabled {\n color: var(--mj-text-disabled);\n border-color: var(--mj-border-default);\n}\n\n/* ==========================================================================\n SECTION 3: Bottom Split - Chart + Feed\n ========================================================================== */\n.bottom-split {\n display: flex;\n gap: 20px;\n}\n\n.panel-title {\n margin: 0 0 16px 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.panel-title i {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n/* Bar Chart Panel (left, 60%) */\n.chart-panel {\n flex: 6;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.bar-chart {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 12px;\n height: 200px;\n padding-top: 24px;\n}\n.bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n position: relative;\n}\n.bar-count {\n font-size: 10px;\n font-weight: 600;\n color: var(--mj-text-muted);\n margin-bottom: 4px;\n}\n.bar-fill {\n width: 100%;\n max-width: 40px;\n background: linear-gradient(180deg, var(--mj-brand-primary) 0%, var(--mj-color-accent-400) 100%);\n border-radius: 4px 4px 0 0;\n min-height: 2px;\n transition: height 0.3s ease;\n}\n.bar-day {\n font-size: 11px;\n color: var(--mj-text-disabled);\n margin-top: 6px;\n text-align: center;\n}\n\n/* Activity Feed Panel (right, 40%) */\n.feed-panel {\n flex: 4;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n max-height: 340px;\n overflow-y: auto;\n}\n\n.feed-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.feed-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border-radius: 6px;\n transition: background 0.15s;\n}\n.feed-item:hover {\n background: var(--mj-bg-page);\n}\n\n.feed-icon-green { color: var(--mj-color-success-600); }\n.feed-icon-red { color: var(--mj-color-error-600); }\n.feed-icon-amber { color: var(--mj-color-warning-600); }\n\n.feed-details {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.feed-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.feed-meta {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n.feed-badge {\n font-size: 10px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.feed-badge-green { background: var(--mj-status-success-bg); color: var(--mj-color-success-800); }\n.feed-badge-amber { background: var(--mj-color-warning-50); color: var(--mj-color-warning-800); }\n.feed-badge-red { background: var(--mj-status-error-bg); color: var(--mj-status-error); }\n\n.empty-hint {\n color: var(--mj-text-disabled);\n font-size: 13px;\n font-style: italic;\n}\n\n/* ==========================================================================\n Responsive\n ========================================================================== */\n@media (max-width: 1200px) {\n .pipeline-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n .metric-strip {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n@media (max-width: 900px) {\n .pipeline-grid {\n grid-template-columns: 1fr;\n }\n .metric-strip {\n grid-template-columns: repeat(2, 1fr);\n }\n .bottom-split {\n flex-direction: column;\n }\n .card-stats {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 600px) {\n .overview {\n padding: 16px;\n }\n .metric-strip {\n grid-template-columns: 1fr;\n }\n}\n"] }]
|
|
750
|
+
args: [{ standalone: false, selector: 'app-integration-overview', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Integration Overview\"\n Icon=\"fa-solid fa-gauge-high\"\n Subtitle=\"Health, throughput, and activity across all integrations\">\n @if (Summaries.length > 0) {\n <div meta>\n <mj-stat-badge [Count]=\"KPIs.TotalIntegrations\" Label=\"integrations\"></mj-stat-badge>\n </div>\n }\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\"></mj-refresh-button>\n </div>\n </mj-page-header>\n\n <mj-page-body>\n<div class=\"overview\">\n <!-- Notification Banner -->\n @if (Notification) {\n <div class=\"notification\" [class.notification-success]=\"Notification.Type === 'success'\"\n [class.notification-error]=\"Notification.Type === 'error'\">\n @if (Notification.Type === 'success') {\n <i class=\"fa-solid fa-circle-check\"></i>\n } @else {\n <i class=\"fa-solid fa-circle-xmark\"></i>\n }\n <span class=\"notification-message\">{{ Notification.Message }}</span>\n <button class=\"notification-dismiss\" (click)=\"DismissNotification()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n\n @if (IsLoading) {\n <mj-loading text=\"Loading integrations...\" size=\"medium\"></mj-loading>\n } @else if (Summaries.length === 0) {\n <mj-empty-state Icon=\"fa-solid fa-plug-circle-xmark\"\n Title=\"No Integrations Configured\"\n Message=\"Set up your first integration using the Connection Studio to start syncing data.\" />\n } @else {\n <!-- ====================================================================\n SECTION 1: Metric Strip\n ==================================================================== -->\n <div class=\"metric-strip\">\n <!-- Total Pipelines -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-blue\">\n <i class=\"fa-solid fa-diagram-project\"></i>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ KPIs.TotalIntegrations }}</span>\n <span class=\"metric-label\">Total Pipelines</span>\n </div>\n </div>\n\n <!-- Records Today -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-green\">\n <i class=\"fa-solid fa-database\"></i>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ FormatNumber(KPIs.RecordsSyncedToday) }}</span>\n <span class=\"metric-label\">Records Today</span>\n </div>\n </div>\n\n <!-- Success Rate (with conic gradient ring) -->\n <div class=\"metric-card\">\n <div class=\"metric-ring\" [style.background]=\"SuccessRateGradient\">\n <div class=\"metric-ring-inner\">\n <span class=\"metric-ring-value\">{{ SuccessRate }}%</span>\n </div>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value metric-value-indigo\">{{ SuccessRate }}%</span>\n <span class=\"metric-label\">Success Rate</span>\n </div>\n </div>\n\n <!-- Active Now -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-emerald\">\n <i class=\"fa-solid fa-bolt\"></i>\n @if (ActiveCount > 0) {\n <span class=\"pulse-dot\"></span>\n }\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ ActiveCount }}</span>\n <span class=\"metric-label\">Active Now</span>\n </div>\n </div>\n\n <!-- Avg Duration -->\n <div class=\"metric-card\">\n <div class=\"metric-icon metric-icon-orange\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"metric-body\">\n <span class=\"metric-value\">{{ FormatDuration(KPIs.AverageSyncDurationMs) }}</span>\n <span class=\"metric-label\">Avg Duration</span>\n </div>\n </div>\n </div>\n\n <!-- ====================================================================\n SECTION 2: Pipeline Health Grid\n ==================================================================== -->\n <div class=\"section-heading\">\n <h3><i class=\"fa-solid fa-heart-pulse\"></i> Pipeline Health</h3>\n <button class=\"refresh-btn\" (click)=\"Refresh()\">\n <i class=\"fa-solid fa-arrows-rotate\"></i> Refresh\n </button>\n </div>\n\n <div class=\"pipeline-grid\">\n @for (summary of Summaries; track summary.Integration.ID) {\n <div [class]=\"GetCardBorderClass(summary.StatusColor)\">\n <!-- Card Header -->\n <div class=\"card-header\">\n <div class=\"card-header-left\">\n <div class=\"card-icon-wrap\">\n <i [class]=\"GetIntegrationIcon(summary)\"></i>\n </div>\n <div class=\"card-title-wrap\">\n <span class=\"card-title\">{{ summary.Integration.Name }}</span>\n <span class=\"card-subtitle\">{{ summary.SourceType?.Name ?? 'Custom' }}</span>\n </div>\n </div>\n <div [class]=\"GetStatusDotClass(summary.StatusColor)\"\n [title]=\"GetStatusLabel(summary.StatusColor)\">\n @if (summary.LatestRun?.Status === 'In Progress') {\n <span class=\"dot-pulse\"></span>\n }\n </div>\n </div>\n\n <!-- Pipeline Flow Visual -->\n <div class=\"pipeline-flow\">\n <div class=\"flow-node\">\n <i [class]=\"GetIntegrationIcon(summary)\"></i>\n </div>\n <div class=\"flow-arrow\">\n <div class=\"flow-line\"></div>\n <span class=\"flow-count\">{{ GetEntityMapCount(summary.Integration.ID) }} maps</span>\n <div class=\"flow-line\"></div>\n </div>\n <div class=\"flow-node flow-node-mj\">\n <i class=\"fa-solid fa-layer-group\"></i>\n </div>\n </div>\n\n <!-- Stats Row -->\n <div class=\"card-stats\">\n <div class=\"stat-item\">\n <span class=\"stat-label\">Last sync</span>\n <span class=\"stat-value\">{{ summary.RelativeTime }}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Records</span>\n <span class=\"stat-value\">{{ FormatNumber(summary.LatestRun?.TotalRecords ?? 0) }}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Duration</span>\n <span class=\"stat-value\">{{ FormatDuration(summary.DurationMs) }}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Errors</span>\n <span class=\"stat-value\" [class.stat-error]=\"summary.TotalErrors > 0\">\n {{ summary.TotalErrors }}\n </span>\n </div>\n </div>\n\n <!-- Sparkline (last 5 runs) -->\n <div class=\"sparkline-row\">\n <span class=\"sparkline-label\">Recent runs</span>\n <div class=\"sparkline-dots\">\n @for (run of summary.RecentRuns; track run.ID) {\n <span [class]=\"GetSparklineDotClass(run)\"\n [title]=\"GetSparklineTooltip(run)\"></span>\n }\n @if (summary.RecentRuns.length === 0) {\n <span class=\"sparkline-none\">No runs</span>\n }\n </div>\n </div>\n\n <!-- Card Footer -->\n <div class=\"card-footer\">\n @if (IsRunning(summary.Integration.ID)) {\n <button class=\"btn-sync btn-sync-disabled\" disabled>\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running...\n </button>\n } @else {\n <button class=\"btn-sync\"\n (click)=\"RunSync(summary.Integration.ID)\"\n [disabled]=\"!summary.Integration.IsActive || RunningIntegrationID !== null\">\n <i class=\"fa-solid fa-play\"></i> Sync Now\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- ====================================================================\n SECTION 3: Bottom - Bar Chart & Activity Feed\n ==================================================================== -->\n <div class=\"bottom-split\">\n <!-- Bar Chart (left, 60%) -->\n <div class=\"chart-panel\">\n <h3 class=\"panel-title\"><i class=\"fa-solid fa-chart-bar\"></i> Records Synced (7 days)</h3>\n @if (DailyCounts.length === 0) {\n <p class=\"empty-hint\">No data available.</p>\n } @else {\n <div class=\"bar-chart\">\n @for (day of DailyCounts; track day.Date) {\n <div class=\"bar-column\">\n <div class=\"bar-count\">{{ FormatNumber(day.Records) }}</div>\n <div class=\"bar-fill\" [style.height.%]=\"BarHeight(day.Records)\"></div>\n <div class=\"bar-day\">{{ day.Label }}</div>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Activity Feed (right, 40%) -->\n <div class=\"feed-panel\">\n <h3 class=\"panel-title\"><i class=\"fa-solid fa-stream\"></i> Recent Activity</h3>\n @if (ActivityFeed.length === 0) {\n <p class=\"empty-hint\">No recent activity.</p>\n } @else {\n <div class=\"feed-list\">\n @for (item of ActivityFeed; track item.RunID) {\n <div class=\"feed-item\">\n <i [class]=\"ActivityStatusIcon(item.Status)\"\n [class.feed-icon-green]=\"item.StatusColor === 'green'\"\n [class.feed-icon-red]=\"item.StatusColor === 'red'\"\n [class.feed-icon-amber]=\"item.StatusColor === 'amber'\">\n </i>\n <div class=\"feed-details\">\n <span class=\"feed-name\">{{ item.IntegrationName }}</span>\n <span class=\"feed-meta\">{{ item.TotalRecords | number }} records · {{ item.RelativeTime }}</span>\n </div>\n <span class=\"feed-badge\" [class]=\"'feed-badge-' + item.StatusColor\">\n {{ item.Status }}\n </span>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n</div>\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/* ==========================================================================\n Integration Overview - Root\n ========================================================================== */\n.overview {\n /* padding + flex/scroll now owned by <mj-page-body>; keep max-width for\n comfortable reading on ultra-wide displays. */\n max-width: 1440px;\n margin: 0 auto;\n}\n\n/* Slot passthrough now in <mj-page-header>; stat badges now via <mj-stat-badge>. */\n\n/* ==========================================================================\n Notification Banner\n ========================================================================== */\n.notification {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n font-weight: 500;\n animation: slideDown 0.25s ease-out;\n}\n.notification i:first-child { font-size: 16px; }\n.notification-message { flex: 1; }\n.notification-success {\n background: var(--mj-status-success-bg);\n color: var(--mj-color-success-800);\n border: 1px solid var(--mj-color-success-200);\n}\n.notification-error {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n border: 1px solid var(--mj-color-error-200);\n}\n.notification-dismiss {\n background: none;\n border: none;\n cursor: pointer;\n color: inherit;\n opacity: 0.5;\n font-size: 14px;\n padding: 4px;\n line-height: 1;\n}\n.notification-dismiss:hover { opacity: 1; }\n\n@keyframes slideDown {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ==========================================================================\n Empty State\n ========================================================================== */\n\n/* ==========================================================================\n SECTION 1: Metric Strip\n ========================================================================== */\n.metric-strip {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 16px;\n margin-bottom: 32px;\n}\n\n.metric-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 18px 16px;\n border-radius: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n}\n.metric-card:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n transform: translateY(-1px);\n}\n\n.metric-icon {\n position: relative;\n width: 44px;\n height: 44px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n}\n.metric-icon-blue { background: var(--mj-status-info-bg); color: var(--mj-brand-primary); }\n.metric-icon-green { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-emerald { background: var(--mj-status-success-bg); color: var(--mj-color-success-600); }\n.metric-icon-orange { background: var(--mj-color-warning-50); color: var(--mj-color-warning-600); }\n\n.metric-body {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.metric-value {\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--mj-text-primary);\n}\n.metric-value-indigo { color: var(--mj-color-indigo-500); }\n\n.metric-label {\n font-size: 11px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Conic gradient ring for success rate */\n.metric-ring {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.metric-ring-inner {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.metric-ring-value {\n font-size: 8px;\n font-weight: 700;\n color: var(--mj-color-indigo-500);\n}\n\n/* Pulse dot for active syncs */\n.pulse-dot {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--mj-color-success-600);\n animation: pulse 1.5s ease-in-out infinite;\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.4); }\n}\n\n/* ==========================================================================\n SECTION 2: Pipeline Health Grid\n ========================================================================== */\n.section-heading {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.section-heading h3 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.section-heading h3 i {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n.refresh-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Don't let the button shrink/overflow the heading row on narrow widths. */\n flex-shrink: 0;\n box-sizing: border-box;\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n.refresh-btn:hover {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-status-info-border);\n}\n\n.pipeline-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 20px;\n margin-bottom: 32px;\n}\n\n.pipeline-card {\n position: relative;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n /* Clip children to the card's rounded border so footer buttons (.btn-sync)\n and any flush-edge content can't poke past the rounded corners. box-shadow\n is drawn outside the border-box, so the hover shadow below is unaffected. */\n overflow: hidden;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n transition: box-shadow 0.2s, transform 0.2s;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.pipeline-card:hover {\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n}\n\n.pipeline-card-error {\n border-left: 3px solid var(--mj-status-error);\n}\n\n/* Card Header */\n.card-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n}\n.card-header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n.card-icon-wrap {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n}\n.card-title-wrap {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.card-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.card-subtitle {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n/* Status Dot */\n.status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-top: 4px;\n position: relative;\n}\n.status-dot-green { background: var(--mj-color-success-600); }\n.status-dot-amber { background: var(--mj-status-warning); }\n.status-dot-red { background: var(--mj-status-error); }\n.status-dot-gray { background: var(--mj-color-neutral-300); }\n\n.dot-pulse {\n position: absolute;\n inset: -3px;\n border-radius: 50%;\n border: 2px solid var(--mj-status-warning);\n animation: dotPulse 1.5s ease-in-out infinite;\n}\n@keyframes dotPulse {\n 0%, 100% { opacity: 0.3; transform: scale(1); }\n 50% { opacity: 0; transform: scale(1.6); }\n}\n\n/* Pipeline Flow Visual */\n.pipeline-flow {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 8px 0;\n}\n.flow-node {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n flex-shrink: 0;\n}\n.flow-node-mj {\n background: var(--mj-status-info-bg);\n color: var(--mj-brand-primary);\n}\n.flow-arrow {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 4px;\n}\n.flow-line {\n flex: 1;\n height: 2px;\n background: var(--mj-border-default);\n}\n.flow-count {\n font-size: 10px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n padding: 2px 8px;\n background: var(--mj-bg-page);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n font-weight: 500;\n}\n\n/* Card Stats */\n.card-stats {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n}\n.stat-item {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n}\n.stat-label {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n.stat-value {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.stat-error {\n color: var(--mj-status-error);\n}\n\n/* Sparkline Row */\n.sparkline-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.sparkline-label {\n font-size: 10px;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n white-space: nowrap;\n}\n.sparkline-dots {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.spark-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n display: inline-block;\n}\n.spark-green { background: var(--mj-color-success-600); }\n.spark-red { background: var(--mj-status-error); }\n.spark-amber { background: var(--mj-status-warning); }\n.spark-gray { background: var(--mj-color-neutral-300); }\n.sparkline-none {\n font-size: 10px;\n color: var(--mj-color-neutral-300);\n font-style: italic;\n}\n\n/* Card Footer */\n.card-footer {\n display: flex;\n justify-content: flex-end;\n /* min-width:0 lets the footer shrink so its button is bounded by the card's\n content box and can't be pushed past the right/bottom padding edge. */\n min-width: 0;\n padding-top: 4px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.btn-sync {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Keep the button bounded by its container so it stays inside the card. */\n max-width: 100%;\n box-sizing: border-box;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n}\n.btn-sync:hover:not(:disabled) {\n background: var(--mj-status-info-bg);\n border-color: var(--mj-status-info-border);\n}\n.btn-sync:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.btn-sync-disabled {\n color: var(--mj-text-disabled);\n border-color: var(--mj-border-default);\n}\n\n/* ==========================================================================\n SECTION 3: Bottom Split - Chart + Feed\n ========================================================================== */\n.bottom-split {\n display: flex;\n gap: 20px;\n}\n\n.panel-title {\n margin: 0 0 16px 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n.panel-title i {\n margin-right: 8px;\n color: var(--mj-text-muted);\n}\n\n/* Bar Chart Panel (left, 60%) */\n.chart-panel {\n flex: 6;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.bar-chart {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 12px;\n height: 200px;\n padding-top: 24px;\n}\n.bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n position: relative;\n}\n.bar-count {\n font-size: 10px;\n font-weight: 600;\n color: var(--mj-text-muted);\n margin-bottom: 4px;\n}\n.bar-fill {\n width: 100%;\n max-width: 40px;\n background: linear-gradient(180deg, var(--mj-brand-primary) 0%, var(--mj-color-accent-400) 100%);\n border-radius: 4px 4px 0 0;\n min-height: 2px;\n transition: height 0.3s ease;\n}\n.bar-day {\n font-size: 11px;\n color: var(--mj-text-disabled);\n margin-top: 6px;\n text-align: center;\n}\n\n/* Activity Feed Panel (right, 40%) */\n.feed-panel {\n flex: 4;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n max-height: 340px;\n overflow-y: auto;\n}\n\n.feed-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.feed-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border-radius: 6px;\n transition: background 0.15s;\n}\n.feed-item:hover {\n background: var(--mj-bg-page);\n}\n\n.feed-icon-green { color: var(--mj-color-success-600); }\n.feed-icon-red { color: var(--mj-color-error-600); }\n.feed-icon-amber { color: var(--mj-color-warning-600); }\n\n.feed-details {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.feed-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.feed-meta {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n.feed-badge {\n font-size: 10px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.feed-badge-green { background: var(--mj-status-success-bg); color: var(--mj-color-success-800); }\n.feed-badge-amber { background: var(--mj-color-warning-50); color: var(--mj-color-warning-800); }\n.feed-badge-red { background: var(--mj-status-error-bg); color: var(--mj-status-error); }\n\n.empty-hint {\n color: var(--mj-text-disabled);\n font-size: 13px;\n font-style: italic;\n}\n\n/* ==========================================================================\n Responsive\n ========================================================================== */\n@media (max-width: 1200px) {\n .pipeline-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n .metric-strip {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n@media (max-width: 900px) {\n .pipeline-grid {\n grid-template-columns: 1fr;\n }\n .metric-strip {\n grid-template-columns: repeat(2, 1fr);\n }\n .bottom-split {\n flex-direction: column;\n }\n .card-stats {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 600px) {\n .overview {\n padding: 16px;\n }\n .metric-strip {\n grid-template-columns: 1fr;\n }\n}\n"] }]
|
|
751
751
|
}], null, null); })();
|
|
752
752
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(OverviewComponent, { className: "OverviewComponent", filePath: "src/Integration/components/overview/overview.component.ts", lineNumber: 41 }); })();
|
|
753
753
|
export function LoadOverviewComponent() {
|