github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/shared/services/extensions-service.ts (about) 1 import * as React from 'react'; 2 import * as minimatch from 'minimatch'; 3 4 import {Application, ApplicationTree, State} from '../models'; 5 6 type ExtensionsEventType = 'resource' | 'systemLevel' | 'appView' | 'statusPanel' | 'topBar'; 7 type ExtensionsType = ResourceTabExtension | SystemLevelExtension | AppViewExtension | StatusPanelExtension | TopBarActionMenuExt; 8 9 class ExtensionsEventTarget { 10 private listeners: Map<ExtensionsEventType, Array<(extension: ExtensionsType) => void>> = new Map(); 11 12 addEventListener(eventName: ExtensionsEventType, listener: (extension: ExtensionsType) => void) { 13 if (!this.listeners.has(eventName)) { 14 this.listeners.set(eventName, []); 15 } 16 this.listeners.get(eventName)?.push(listener); 17 } 18 19 removeEventListener(eventName: ExtensionsEventType, listenerToRemove: (extension: ExtensionsType) => void) { 20 const listeners = this.listeners.get(eventName); 21 if (!listeners) return; 22 23 const filteredListeners = listeners.filter(listener => listener !== listenerToRemove); 24 this.listeners.set(eventName, filteredListeners); 25 } 26 27 emit(eventName: ExtensionsEventType, extension: ExtensionsType) { 28 this.listeners.get(eventName)?.forEach(listener => listener(extension)); 29 } 30 } 31 32 const extensions = { 33 eventTarget: new ExtensionsEventTarget(), 34 resourceExtentions: new Array<ResourceTabExtension>(), 35 systemLevelExtensions: new Array<SystemLevelExtension>(), 36 appViewExtensions: new Array<AppViewExtension>(), 37 statusPanelExtensions: new Array<StatusPanelExtension>(), 38 topBarActionMenuExts: new Array<TopBarActionMenuExt>() 39 }; 40 41 function registerResourceExtension(component: ExtensionComponent, group: string, kind: string, tabTitle: string, opts?: {icon: string}) { 42 const ext = {component, group, kind, title: tabTitle, icon: opts?.icon}; 43 extensions.resourceExtentions.push(ext); 44 extensions.eventTarget.emit('resource', ext); 45 } 46 47 function registerSystemLevelExtension(component: ExtensionComponent, title: string, path: string, icon: string) { 48 const ext = {component, title, icon, path}; 49 extensions.systemLevelExtensions.push(ext); 50 extensions.eventTarget.emit('systemLevel', ext); 51 } 52 53 function registerAppViewExtension(component: ExtensionComponent, title: string, icon: string) { 54 const ext = {component, title, icon}; 55 extensions.appViewExtensions.push(ext); 56 extensions.eventTarget.emit('appView', ext); 57 } 58 59 function registerStatusPanelExtension(component: StatusPanelExtensionComponent, title: string, id: string, flyout?: ExtensionComponent) { 60 const ext = {component, flyout, title, id}; 61 extensions.statusPanelExtensions.push(ext); 62 extensions.eventTarget.emit('statusPanel', ext); 63 } 64 65 function registerTopBarActionMenuExt( 66 component: TopBarActionMenuExtComponent, 67 title: string, 68 id: string, 69 flyout: ExtensionComponent, 70 shouldDisplay: (app?: Application) => boolean = () => true, 71 iconClassName?: string, 72 isMiddle = false 73 ) { 74 const ext = {component, flyout, shouldDisplay, title, id, iconClassName, isMiddle}; 75 extensions.topBarActionMenuExts.push(ext); 76 extensions.eventTarget.emit('topBar', ext); 77 } 78 79 let legacyInitialized = false; 80 81 function initLegacyExtensions() { 82 if (legacyInitialized) { 83 return; 84 } 85 legacyInitialized = true; 86 const resources = (window as any).extensions.resources; 87 Object.keys(resources).forEach(key => { 88 const [group, kind] = key.split('/'); 89 registerResourceExtension(resources[key].component, group, kind, 'More'); 90 }); 91 } 92 93 export interface ResourceTabExtension { 94 title: string; 95 group: string; 96 kind: string; 97 component: ExtensionComponent; 98 icon?: string; 99 } 100 101 export interface SystemLevelExtension { 102 title: string; 103 component: SystemExtensionComponent; 104 icon?: string; 105 path?: string; 106 } 107 108 export interface AppViewExtension { 109 component: AppViewExtensionComponent; 110 title: string; 111 icon?: string; 112 } 113 114 export interface StatusPanelExtension { 115 component: StatusPanelExtensionComponent; 116 flyout?: StatusPanelExtensionFlyoutComponent; 117 title: string; 118 id: string; 119 } 120 121 export interface TopBarActionMenuExt { 122 component: TopBarActionMenuExtComponent; 123 flyout: TopBarActionMenuExtFlyoutComponent; 124 shouldDisplay: (app: Application) => boolean; 125 title: string; 126 id: string; 127 iconClassName?: string; 128 isMiddle?: boolean; 129 isNarrow?: boolean; 130 } 131 132 export type ExtensionComponent = React.ComponentType<ExtensionComponentProps>; 133 export type SystemExtensionComponent = React.ComponentType; 134 export type AppViewExtensionComponent = React.ComponentType<AppViewComponentProps>; 135 export type StatusPanelExtensionComponent = React.ComponentType<StatusPanelComponentProps>; 136 export type StatusPanelExtensionFlyoutComponent = React.ComponentType<StatusPanelFlyoutProps>; 137 export type TopBarActionMenuExtComponent = React.ComponentType<TopBarActionMenuExtComponentProps>; 138 export type TopBarActionMenuExtFlyoutComponent = React.ComponentType<TopBarActionMenuExtFlyoutProps>; 139 140 export interface Extension { 141 component: ExtensionComponent; 142 } 143 144 export interface ExtensionComponentProps { 145 resource: State; 146 tree: ApplicationTree; 147 application: Application; 148 } 149 150 export interface AppViewComponentProps { 151 application: Application; 152 tree: ApplicationTree; 153 } 154 155 export interface StatusPanelComponentProps { 156 application: Application; 157 openFlyout: () => any; 158 } 159 160 export interface TopBarActionMenuExtComponentProps { 161 application: Application; 162 tree: ApplicationTree; 163 openFlyout: () => any; 164 } 165 166 export interface StatusPanelFlyoutProps { 167 application: Application; 168 tree: ApplicationTree; 169 } 170 171 export interface TopBarActionMenuExtFlyoutProps { 172 application: Application; 173 tree: ApplicationTree; 174 } 175 176 export class ExtensionsService { 177 public addEventListener(evtType: ExtensionsEventType, cb: (ext: ExtensionsType) => void) { 178 extensions.eventTarget.addEventListener(evtType, cb); 179 } 180 181 public removeEventListener(evtType: ExtensionsEventType, cb: (ext: ExtensionsType) => void) { 182 extensions.eventTarget.removeEventListener(evtType, cb); 183 } 184 185 public getResourceTabs(group: string, kind: string): ResourceTabExtension[] { 186 initLegacyExtensions(); 187 const items = extensions.resourceExtentions.filter(extension => minimatch(group, extension.group) && minimatch(kind, extension.kind)).slice(); 188 return items.sort((a, b) => a.title.localeCompare(b.title)); 189 } 190 191 public getSystemExtensions(): SystemLevelExtension[] { 192 return extensions.systemLevelExtensions.slice(); 193 } 194 195 public getAppViewExtensions(): AppViewExtension[] { 196 return extensions.appViewExtensions.slice(); 197 } 198 199 public getStatusPanelExtensions(): StatusPanelExtension[] { 200 return extensions.statusPanelExtensions.slice(); 201 } 202 public getActionMenuExtensions(): TopBarActionMenuExt[] { 203 return extensions.topBarActionMenuExts.slice(); 204 } 205 } 206 207 ((window: any) => { 208 // deprecated: kept for backwards compatibility 209 window.extensions = {resources: {}}; 210 window.extensionsAPI = { 211 registerResourceExtension, 212 registerSystemLevelExtension, 213 registerAppViewExtension, 214 registerStatusPanelExtension, 215 registerTopBarActionMenuExt 216 }; 217 })(window);