@quazardous/qdadm 1.19.3

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.
Files changed (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,501 @@
1
+ /**
2
+ * AuthCollector - Debug collector for authentication state
3
+ *
4
+ * This collector displays current authentication information:
5
+ * - Current user details
6
+ * - Permissions/roles
7
+ * - Token info (expiry, claims)
8
+ * - Auth adapter status
9
+ *
10
+ * Only shows content when auth is configured in the application.
11
+ */
12
+
13
+ import {
14
+ Collector,
15
+ type CollectorContext,
16
+ type CollectorEntry,
17
+ type CollectorManifest,
18
+ type CollectorOptions,
19
+ type CollectorSnapshot,
20
+ } from './Collector'
21
+ import type { ISessionAuthAdapter, AuthUser } from '../../auth/SessionAuthAdapter'
22
+ import type { SecurityChecker } from '../../entity/auth/SecurityChecker'
23
+
24
+ /**
25
+ * Auth event types
26
+ */
27
+ export type AuthEventType = 'login' | 'logout' | 'impersonate' | 'impersonate-stop' | 'login-error'
28
+
29
+ /**
30
+ * Auth event entry
31
+ */
32
+ export interface AuthEvent {
33
+ type: AuthEventType
34
+ timestamp: Date
35
+ id: number
36
+ seen: boolean
37
+ data: unknown
38
+ }
39
+
40
+ /**
41
+ * Auth entry types
42
+ */
43
+ export interface AuthEntry extends CollectorEntry {
44
+ type: 'status' | 'user' | 'impersonated' | 'token' | 'user-permissions' | 'hierarchy' | 'role-permissions' | 'permissions' | 'adapter' | 'error'
45
+ label?: string
46
+ message?: string
47
+ data?: unknown
48
+ }
49
+
50
+ /**
51
+ * Auth collector options
52
+ */
53
+ export interface AuthCollectorOptions extends CollectorOptions {
54
+ eventTtl?: number
55
+ }
56
+
57
+ /**
58
+ * Extended context for auth collector
59
+ */
60
+ interface AuthCollectorContext extends CollectorContext {
61
+ auth?: ISessionAuthAdapter
62
+ authAdapter?: ISessionAuthAdapter
63
+ security?: SecurityChecker
64
+ permissionRegistry?: { size: number; getKeys: () => string[] }
65
+ _kernel?: { permissionRegistry?: { size: number; getKeys: () => string[] } }
66
+ }
67
+
68
+ /**
69
+ * Collector for authentication state visualization
70
+ */
71
+ export class AuthCollector extends Collector<AuthEntry> {
72
+ static override collectorName = 'auth'
73
+ static override records = false
74
+
75
+ private _authAdapter: ISessionAuthAdapter | null = null
76
+ private _securityChecker: SecurityChecker | null = null
77
+ private _signalCleanups: Array<() => void> = []
78
+ private _recentEvents: AuthEvent[] = []
79
+ private _eventTtl: number
80
+ private _expiryTimer: ReturnType<typeof setTimeout> | null = null
81
+
82
+ constructor(options: AuthCollectorOptions = {}) {
83
+ super(options)
84
+ this._eventTtl = options.eventTtl ?? 60000
85
+ }
86
+
87
+ protected override _doInstall(ctx: CollectorContext): void {
88
+ this._ctx = ctx
89
+ const authCtx = ctx as AuthCollectorContext
90
+ this._authAdapter = authCtx.auth ?? authCtx.authAdapter ?? null
91
+ this._securityChecker = authCtx.security ?? null
92
+ this._setupSignals()
93
+ }
94
+
95
+ private _setupSignals(): void {
96
+ const signals = this._ctx?.signals
97
+ if (!signals) {
98
+ setTimeout(() => this._setupSignals(), 100)
99
+ return
100
+ }
101
+
102
+ const loginCleanup = signals.on('auth:login', (event) => {
103
+ const data = (event.data ?? event) as { user?: AuthUser }
104
+ const user = data?.user ?? (this._authAdapter as { getUser?: () => AuthUser })?.getUser?.()
105
+ this._addEvent('login', { user })
106
+ })
107
+ this._signalCleanups.push(loginCleanup)
108
+
109
+ const logoutCleanup = signals.on('auth:logout', () => {
110
+ this._addEvent('logout')
111
+ })
112
+ this._signalCleanups.push(logoutCleanup)
113
+
114
+ const impersonateCleanup = signals.on('auth:impersonate', (payload) => {
115
+ this._addEvent('impersonate', payload.data)
116
+ })
117
+ this._signalCleanups.push(impersonateCleanup)
118
+
119
+ const impersonateStopCleanup = signals.on('auth:impersonate:stop', (payload) => {
120
+ this._addEvent('impersonate-stop', payload.data)
121
+ })
122
+ this._signalCleanups.push(impersonateStopCleanup)
123
+
124
+ const loginErrorCleanup = signals.on('auth:login:error', (payload) => {
125
+ this._addEvent('login-error', payload.data)
126
+ })
127
+ this._signalCleanups.push(loginErrorCleanup)
128
+ }
129
+
130
+ private _addEvent(type: AuthEventType, data: unknown = null): void {
131
+ this._recentEvents.unshift({
132
+ type,
133
+ timestamp: new Date(),
134
+ id: Date.now(),
135
+ seen: false,
136
+ data
137
+ })
138
+ this._scheduleExpiry()
139
+ this.notifyChange()
140
+ }
141
+
142
+ private _scheduleExpiry(): void {
143
+ if (this._expiryTimer) {
144
+ clearTimeout(this._expiryTimer)
145
+ this._expiryTimer = null
146
+ }
147
+
148
+ if (this._recentEvents.length === 0) return
149
+
150
+ const now = Date.now()
151
+ const oldest = this._recentEvents[this._recentEvents.length - 1]
152
+ if (!oldest) return
153
+ const age = now - oldest.timestamp.getTime()
154
+ const delay = Math.max(100, this._eventTtl - age)
155
+
156
+ this._expiryTimer = setTimeout(() => {
157
+ this._expireOldEvents()
158
+ }, delay)
159
+ }
160
+
161
+ private _expireOldEvents(): void {
162
+ this._expiryTimer = null
163
+ const now = Date.now()
164
+ const before = this._recentEvents.length
165
+ this._recentEvents = this._recentEvents.filter(
166
+ e => (now - e.timestamp.getTime()) < this._eventTtl
167
+ )
168
+ if (this._recentEvents.length < before) {
169
+ this.notifyChange()
170
+ }
171
+ if (this._recentEvents.length > 0) {
172
+ this._scheduleExpiry()
173
+ }
174
+ }
175
+
176
+ protected override _doUninstall(): void {
177
+ if (this._expiryTimer) {
178
+ clearTimeout(this._expiryTimer)
179
+ this._expiryTimer = null
180
+ }
181
+ for (const cleanup of this._signalCleanups) {
182
+ if (typeof cleanup === 'function') cleanup()
183
+ }
184
+ this._signalCleanups = []
185
+ this._authAdapter = null
186
+ this._securityChecker = null
187
+ this._ctx = null
188
+ }
189
+
190
+ override getBadge(): number {
191
+ return this._recentEvents.filter(e => !e.seen).length
192
+ }
193
+
194
+ hasActivity(): boolean {
195
+ return this._recentEvents.some(e => !e.seen)
196
+ }
197
+
198
+ getRecentEvents(): AuthEvent[] {
199
+ return this._recentEvents
200
+ }
201
+
202
+ getLastEvent(): AuthEventType | null {
203
+ return this._recentEvents[0]?.type ?? null
204
+ }
205
+
206
+ markSeen(): void {
207
+ for (const event of this._recentEvents) {
208
+ event.seen = true
209
+ }
210
+ }
211
+
212
+ clearEvents(): void {
213
+ this._recentEvents = []
214
+ this.notifyChange()
215
+ }
216
+
217
+ override getEntries(): AuthEntry[] {
218
+ const authCtx = this._ctx as AuthCollectorContext
219
+ const authAdapter = authCtx?.auth ?? authCtx?.authAdapter ?? this._authAdapter
220
+ if (!authAdapter) {
221
+ return [{ timestamp: Date.now(), type: 'status', message: 'No auth adapter configured' }]
222
+ }
223
+
224
+ const entries: AuthEntry[] = []
225
+ const adapter = authAdapter as ISessionAuthAdapter & {
226
+ getUser?: () => AuthUser | null
227
+ getToken?: () => string | null
228
+ getPermissions?: () => string[]
229
+ isImpersonating?: () => boolean
230
+ getOriginalUser?: () => AuthUser | null
231
+ impersonate?: (user: AuthUser) => void
232
+ }
233
+
234
+ // User info
235
+ try {
236
+ const user = adapter.getUser?.()
237
+ const securityChecker = authCtx?.security ?? this._securityChecker
238
+
239
+ if (user) {
240
+ const isImpersonating = adapter.isImpersonating?.() ?? false
241
+ const originalUser = isImpersonating ? adapter.getOriginalUser?.() : null
242
+
243
+ const realUser = originalUser ?? user
244
+ const realRoles = this._normalizeRoles((realUser as AuthUser & { role?: string }).roles ?? (realUser as AuthUser & { role?: string }).role)
245
+ const realPermissions = this._getEffectivePermissions(securityChecker, realRoles)
246
+
247
+ entries.push({
248
+ timestamp: Date.now(),
249
+ type: 'user',
250
+ label: 'Current User',
251
+ data: {
252
+ id: realUser.id,
253
+ username: realUser.username ?? realUser.name ?? realUser.email,
254
+ email: realUser.email,
255
+ roles: realRoles,
256
+ permissions: realPermissions
257
+ }
258
+ })
259
+
260
+ if (isImpersonating) {
261
+ const impersonatedRoles = this._normalizeRoles((user as AuthUser & { role?: string }).roles ?? (user as AuthUser & { role?: string }).role)
262
+ const impersonatedPermissions = this._getEffectivePermissions(securityChecker, impersonatedRoles)
263
+
264
+ entries.push({
265
+ timestamp: Date.now(),
266
+ type: 'impersonated',
267
+ label: 'Impersonated User',
268
+ data: {
269
+ id: user.id,
270
+ username: user.username ?? user.name ?? user.email,
271
+ roles: impersonatedRoles,
272
+ permissions: impersonatedPermissions
273
+ }
274
+ })
275
+ }
276
+ } else {
277
+ const checker = securityChecker as SecurityChecker & { rolesProvider?: { getAnonymousRole?: () => string } }
278
+ const anonymousRole = checker?.rolesProvider?.getAnonymousRole?.() ?? 'ROLE_ANONYMOUS'
279
+ const effectivePermissions = this._getEffectivePermissions(securityChecker, [anonymousRole])
280
+
281
+ entries.push({
282
+ timestamp: Date.now(),
283
+ type: 'user',
284
+ label: 'Anonymous',
285
+ data: {
286
+ role: anonymousRole,
287
+ authenticated: false,
288
+ permissions: effectivePermissions
289
+ }
290
+ })
291
+ }
292
+ } catch (e) {
293
+ entries.push({
294
+ timestamp: Date.now(),
295
+ type: 'error',
296
+ label: 'User Error',
297
+ message: (e as Error).message
298
+ })
299
+ }
300
+
301
+ // Token info
302
+ try {
303
+ const token = adapter.getToken?.()
304
+ if (token) {
305
+ const decoded = this._decodeToken(token)
306
+ entries.push({
307
+ timestamp: Date.now(),
308
+ type: 'token',
309
+ label: 'Token',
310
+ data: {
311
+ preview: token.substring(0, 20) + '...',
312
+ expiresAt: decoded?.exp ? new Date(decoded.exp * 1000).toLocaleString() : 'Unknown',
313
+ issuedAt: decoded?.iat ? new Date(decoded.iat * 1000).toLocaleString() : 'Unknown',
314
+ claims: decoded
315
+ }
316
+ })
317
+ }
318
+ } catch {
319
+ // Token not available
320
+ }
321
+
322
+ // User permissions
323
+ try {
324
+ const userPermissions = adapter.getPermissions?.()
325
+ if (userPermissions && userPermissions.length > 0) {
326
+ entries.push({
327
+ timestamp: Date.now(),
328
+ type: 'user-permissions',
329
+ label: 'User Permissions',
330
+ data: userPermissions
331
+ })
332
+ }
333
+ } catch {
334
+ // Permissions not available
335
+ }
336
+
337
+ // Role hierarchy & permissions
338
+ try {
339
+ const securityChecker = this._securityChecker ?? authCtx?.security
340
+ const checker = securityChecker as SecurityChecker & { roleHierarchy?: { map: Record<string, string[]> }; rolePermissions?: Record<string, string[]> }
341
+ const hierarchy = checker?.roleHierarchy?.map
342
+ if (hierarchy && Object.keys(hierarchy).length > 0) {
343
+ entries.push({
344
+ timestamp: Date.now(),
345
+ type: 'hierarchy',
346
+ label: 'Role Hierarchy',
347
+ data: hierarchy
348
+ })
349
+ }
350
+ const rolePermissions = checker?.rolePermissions
351
+ if (rolePermissions && Object.keys(rolePermissions).length > 0) {
352
+ entries.push({
353
+ timestamp: Date.now(),
354
+ type: 'role-permissions',
355
+ label: 'Role Permissions',
356
+ data: rolePermissions
357
+ })
358
+ }
359
+ } catch {
360
+ // Security checker not available
361
+ }
362
+
363
+ // Registered permissions
364
+ try {
365
+ const permissionRegistry = authCtx?.permissionRegistry
366
+ ?? authCtx?._kernel?.permissionRegistry
367
+ if (permissionRegistry && permissionRegistry.size > 0) {
368
+ const permissions = permissionRegistry.getKeys()
369
+ entries.push({
370
+ timestamp: Date.now(),
371
+ type: 'permissions',
372
+ label: 'Permissions',
373
+ data: permissions
374
+ })
375
+ }
376
+ } catch (e) {
377
+ console.warn('[AuthCollector] Error accessing permissionRegistry:', e)
378
+ }
379
+
380
+ // Adapter info
381
+ entries.push({
382
+ timestamp: Date.now(),
383
+ type: 'adapter',
384
+ label: 'Auth Adapter',
385
+ data: {
386
+ type: adapter.constructor?.name ?? 'Unknown',
387
+ hasUser: !!adapter.getUser,
388
+ hasToken: !!adapter.getToken,
389
+ hasPermissions: !!adapter.getPermissions,
390
+ hasLogin: !!(adapter as { login?: unknown }).login,
391
+ hasLogout: !!(adapter as { logout?: unknown }).logout,
392
+ hasImpersonate: !!adapter.impersonate,
393
+ isImpersonating: adapter.isImpersonating?.() ?? false
394
+ }
395
+ })
396
+
397
+ return entries
398
+ }
399
+
400
+ private _getEffectivePermissions(securityChecker: SecurityChecker | null | undefined, roles: string[]): string[] {
401
+ if (!securityChecker || !roles || roles.length === 0) {
402
+ return []
403
+ }
404
+
405
+ try {
406
+ const checker = securityChecker as SecurityChecker & {
407
+ getUserPermissions?: (user: { roles: string[] }) => string[]
408
+ rolesProvider?: { getPermissions?: (role: string) => string[] }
409
+ roleHierarchy?: { getReachableRoles?: (role: string) => string[] }
410
+ }
411
+
412
+ if (checker.getUserPermissions) {
413
+ const mockUser = { roles }
414
+ const perms = checker.getUserPermissions(mockUser)
415
+ return [...new Set(perms)].sort()
416
+ }
417
+
418
+ const rolesProvider = checker.rolesProvider
419
+ if (!rolesProvider) return []
420
+
421
+ const permissions = new Set<string>()
422
+ for (const role of roles) {
423
+ const reachable = checker.roleHierarchy?.getReachableRoles?.(role) ?? [role]
424
+ for (const r of reachable) {
425
+ const rolePerms = rolesProvider.getPermissions?.(r) ?? []
426
+ for (const perm of rolePerms) {
427
+ permissions.add(perm)
428
+ }
429
+ }
430
+ }
431
+
432
+ return [...permissions].sort()
433
+ } catch (e) {
434
+ console.warn('[AuthCollector] Error getting effective permissions:', e)
435
+ return []
436
+ }
437
+ }
438
+
439
+ private _normalizeRoles(roles: string | string[] | undefined): string[] {
440
+ if (!roles) return []
441
+ const arr = Array.isArray(roles) ? roles : [roles]
442
+ return arr.map(role => {
443
+ if (!role) return null
444
+ const upper = role.toUpperCase()
445
+ return upper.startsWith('ROLE_') ? upper : `ROLE_${upper}`
446
+ }).filter((r): r is string => r !== null)
447
+ }
448
+
449
+ private _decodeToken(token: string): { exp?: number; iat?: number; [key: string]: unknown } | null {
450
+ try {
451
+ const parts = token.split('.')
452
+ if (parts.length !== 3) return null
453
+ const payload = parts[1]
454
+ if (!payload) return null
455
+ const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'))
456
+ return JSON.parse(decoded)
457
+ } catch {
458
+ return null
459
+ }
460
+ }
461
+
462
+ override describe(): CollectorManifest {
463
+ return {
464
+ name: this.name,
465
+ records: false,
466
+ summary:
467
+ 'Surfaces current authentication state: user, roles, permissions, hierarchy, token claims, adapter capabilities, plus a TTL-bounded log of recent auth events.',
468
+ entryShape: {
469
+ type: 'string (user|impersonated|token|user-permissions|hierarchy|role-permissions|permissions|adapter|status|error)',
470
+ label: 'string?',
471
+ message: 'string?',
472
+ data: 'json',
473
+ },
474
+ stateShape: {
475
+ recentEvents: 'AuthEvent[] (TTL-bounded)',
476
+ lastEvent: 'string?',
477
+ },
478
+ actions: this._builtinActionManifests(),
479
+ }
480
+ }
481
+
482
+ override snapshot(): CollectorSnapshot {
483
+ const entries = this.getEntries()
484
+ return {
485
+ name: this.name,
486
+ entries,
487
+ count: entries.length,
488
+ unseen: this.getBadge(),
489
+ state: {
490
+ recentEvents: this._recentEvents.map((e) => ({
491
+ type: e.type,
492
+ timestamp: e.timestamp.toISOString(),
493
+ id: e.id,
494
+ seen: e.seen,
495
+ data: e.data,
496
+ })),
497
+ lastEvent: this.getLastEvent(),
498
+ },
499
+ }
500
+ }
501
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Collector — qdadm re-export.
3
+ *
4
+ * The actual implementation now lives in `@quazardous/qddebug` so it can be
5
+ * shared with qdcms. Existing internal imports (`from './Collector'`)
6
+ * continue to work via this shim.
7
+ */
8
+
9
+ export {
10
+ Collector,
11
+ type CollectorEntry,
12
+ type CollectorContext,
13
+ type CollectorOptions,
14
+ type CollectorManifest,
15
+ type CollectorSnapshot,
16
+ type CollectorAction,
17
+ type CollectorActionManifest,
18
+ type DebugBridgeInterface,
19
+ type NotifyCallback,
20
+ } from '@quazardous/qddebug'
@@ -0,0 +1,14 @@
1
+ /**
2
+ * DebugBridge — qdadm re-export.
3
+ *
4
+ * The actual implementation now lives in `@quazardous/qddebug` so it can be
5
+ * shared with qdcms.
6
+ */
7
+
8
+ export {
9
+ DebugBridge,
10
+ createDebugBridge,
11
+ type DebugBridgeOptions,
12
+ type BridgeManifest,
13
+ type BridgeSnapshot,
14
+ } from '@quazardous/qddebug'