qdadm 0.13.0

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 (82) hide show
  1. package/CHANGELOG.md +270 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/package.json +48 -0
  5. package/src/assets/logo.svg +6 -0
  6. package/src/components/BoolCell.vue +28 -0
  7. package/src/components/dialogs/BulkStatusDialog.vue +43 -0
  8. package/src/components/dialogs/MultiStepDialog.vue +321 -0
  9. package/src/components/dialogs/SimpleDialog.vue +108 -0
  10. package/src/components/dialogs/UnsavedChangesDialog.vue +87 -0
  11. package/src/components/display/CardsGrid.vue +155 -0
  12. package/src/components/display/CopyableId.vue +92 -0
  13. package/src/components/display/EmptyState.vue +114 -0
  14. package/src/components/display/IntensityBar.vue +171 -0
  15. package/src/components/display/RichCardsGrid.vue +220 -0
  16. package/src/components/editors/JsonEditorFoldable.vue +467 -0
  17. package/src/components/editors/JsonStructuredField.vue +218 -0
  18. package/src/components/editors/JsonViewer.vue +91 -0
  19. package/src/components/editors/KeyValueEditor.vue +314 -0
  20. package/src/components/editors/LanguageEditor.vue +245 -0
  21. package/src/components/editors/ScopeEditor.vue +341 -0
  22. package/src/components/editors/VanillaJsonEditor.vue +185 -0
  23. package/src/components/forms/FormActions.vue +104 -0
  24. package/src/components/forms/FormField.vue +64 -0
  25. package/src/components/forms/FormTab.vue +217 -0
  26. package/src/components/forms/FormTabs.vue +108 -0
  27. package/src/components/index.js +44 -0
  28. package/src/components/layout/AppLayout.vue +430 -0
  29. package/src/components/layout/Breadcrumb.vue +106 -0
  30. package/src/components/layout/PageHeader.vue +75 -0
  31. package/src/components/layout/PageLayout.vue +93 -0
  32. package/src/components/lists/ActionButtons.vue +41 -0
  33. package/src/components/lists/ActionColumn.vue +37 -0
  34. package/src/components/lists/FilterBar.vue +53 -0
  35. package/src/components/lists/ListPage.vue +319 -0
  36. package/src/composables/index.js +19 -0
  37. package/src/composables/useApp.js +43 -0
  38. package/src/composables/useAuth.js +49 -0
  39. package/src/composables/useBareForm.js +143 -0
  40. package/src/composables/useBreadcrumb.js +221 -0
  41. package/src/composables/useDirtyState.js +103 -0
  42. package/src/composables/useEntityTitle.js +121 -0
  43. package/src/composables/useForm.js +254 -0
  44. package/src/composables/useGuardStore.js +37 -0
  45. package/src/composables/useJsonSyntax.js +101 -0
  46. package/src/composables/useListPageBuilder.js +1176 -0
  47. package/src/composables/useNavigation.js +89 -0
  48. package/src/composables/usePageBuilder.js +334 -0
  49. package/src/composables/useStatus.js +146 -0
  50. package/src/composables/useSubEditor.js +165 -0
  51. package/src/composables/useTabSync.js +110 -0
  52. package/src/composables/useUnsavedChangesGuard.js +122 -0
  53. package/src/entity/EntityManager.js +540 -0
  54. package/src/entity/index.js +11 -0
  55. package/src/entity/storage/ApiStorage.js +146 -0
  56. package/src/entity/storage/LocalStorage.js +220 -0
  57. package/src/entity/storage/MemoryStorage.js +201 -0
  58. package/src/entity/storage/index.js +10 -0
  59. package/src/index.js +29 -0
  60. package/src/kernel/Kernel.js +234 -0
  61. package/src/kernel/index.js +7 -0
  62. package/src/module/index.js +16 -0
  63. package/src/module/moduleRegistry.js +222 -0
  64. package/src/orchestrator/Orchestrator.js +141 -0
  65. package/src/orchestrator/index.js +8 -0
  66. package/src/orchestrator/useOrchestrator.js +61 -0
  67. package/src/plugin.js +142 -0
  68. package/src/styles/_alerts.css +48 -0
  69. package/src/styles/_code.css +33 -0
  70. package/src/styles/_dialogs.css +17 -0
  71. package/src/styles/_markdown.css +82 -0
  72. package/src/styles/_show-pages.css +84 -0
  73. package/src/styles/index.css +16 -0
  74. package/src/styles/main.css +845 -0
  75. package/src/styles/theme/components.css +286 -0
  76. package/src/styles/theme/index.css +10 -0
  77. package/src/styles/theme/tokens.css +125 -0
  78. package/src/styles/theme/utilities.css +172 -0
  79. package/src/utils/debugInjector.js +261 -0
  80. package/src/utils/formatters.js +165 -0
  81. package/src/utils/index.js +35 -0
  82. package/src/utils/transformers.js +105 -0
@@ -0,0 +1,41 @@
1
+ <script setup>
2
+ /**
3
+ * ActionButtons - Renders action buttons from useTableActions
4
+ *
5
+ * Props:
6
+ * - actions: Array of action objects from getActions(row)
7
+ *
8
+ * Each action object should have:
9
+ * - icon: PrimeVue icon class
10
+ * - tooltip: Tooltip text
11
+ * - severity: Button severity
12
+ * - handler: Click handler function
13
+ * - isDisabled: Boolean disabled state
14
+ */
15
+ import Button from 'primevue/button'
16
+
17
+ defineProps({
18
+ actions: {
19
+ type: Array,
20
+ required: true
21
+ }
22
+ })
23
+ </script>
24
+
25
+ <template>
26
+ <div class="table-actions">
27
+ <Button
28
+ v-for="action in actions"
29
+ :key="action.name"
30
+ :icon="action.icon"
31
+ :severity="action.severity"
32
+ text
33
+ rounded
34
+ :disabled="action.isDisabled"
35
+ @click="action.handler"
36
+ v-tooltip.top="action.tooltip"
37
+ />
38
+ </div>
39
+ </template>
40
+
41
+ <!-- Styles in main.css: .table-actions -->
@@ -0,0 +1,37 @@
1
+ <script setup>
2
+ /**
3
+ * ActionColumn - Reusable table action buttons (edit, delete)
4
+ *
5
+ * Slots:
6
+ * - default: Extra actions (after edit/delete)
7
+ *
8
+ * Emits:
9
+ * - edit: When edit button is clicked
10
+ * - delete: When delete button is clicked
11
+ */
12
+ import Button from 'primevue/button'
13
+
14
+ defineEmits(['edit', 'delete'])
15
+ </script>
16
+
17
+ <template>
18
+ <div class="table-actions">
19
+ <Button
20
+ icon="pi pi-pencil"
21
+ severity="secondary"
22
+ text
23
+ rounded
24
+ @click="$emit('edit')"
25
+ v-tooltip.top="'Edit'"
26
+ />
27
+ <Button
28
+ icon="pi pi-trash"
29
+ severity="danger"
30
+ text
31
+ rounded
32
+ @click="$emit('delete')"
33
+ v-tooltip.top="'Delete'"
34
+ />
35
+ <slot ></slot>
36
+ </div>
37
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup>
2
+ /**
3
+ * FilterBar - Reusable filter bar with search field
4
+ *
5
+ * Props:
6
+ * - search: Current search value (v-model compatible)
7
+ * - placeholder: Search input placeholder
8
+ *
9
+ * Slots:
10
+ * - default: Custom filters (left side, before search)
11
+ *
12
+ * Emits:
13
+ * - update:search: When search value changes
14
+ */
15
+ import { computed } from 'vue'
16
+ import InputText from 'primevue/inputtext'
17
+ import InputIcon from 'primevue/inputicon'
18
+ import IconField from 'primevue/iconfield'
19
+
20
+ const props = defineProps({
21
+ modelValue: {
22
+ type: String,
23
+ default: ''
24
+ },
25
+ placeholder: {
26
+ type: String,
27
+ default: 'Search...'
28
+ }
29
+ })
30
+
31
+ const emit = defineEmits(['update:modelValue'])
32
+
33
+ const searchModel = computed({
34
+ get: () => props.modelValue,
35
+ set: (value) => emit('update:modelValue', value)
36
+ })
37
+ </script>
38
+
39
+ <template>
40
+ <div class="filter-bar">
41
+ <div class="filter-bar-left">
42
+ <IconField>
43
+ <InputIcon class="pi pi-search" />
44
+ <InputText
45
+ v-model="searchModel"
46
+ :placeholder="placeholder"
47
+ :class="{ 'filter-active': searchModel }"
48
+ />
49
+ </IconField>
50
+ <slot ></slot>
51
+ </div>
52
+ </div>
53
+ </template>
@@ -0,0 +1,319 @@
1
+ <script setup>
2
+ /**
3
+ * ListPage - Unified list page component
4
+ *
5
+ * Renders a complete CRUD list page with:
6
+ * - PageHeader with title and create button
7
+ * - CardsGrid for stats/custom cards
8
+ * - FilterBar with search and custom filters
9
+ * - DataTable with actions column
10
+ *
11
+ * Props come from useListPageBuilder composable
12
+ *
13
+ * Filter types:
14
+ * - 'select' (default): Standard dropdown
15
+ * - 'autocomplete': Searchable dropdown with type-ahead
16
+ */
17
+ import { computed, ref, watch } from 'vue'
18
+ import PageHeader from '../layout/PageHeader.vue'
19
+ import CardsGrid from '../display/CardsGrid.vue'
20
+ import FilterBar from './FilterBar.vue'
21
+ import ActionButtons from './ActionButtons.vue'
22
+ import DataTable from 'primevue/datatable'
23
+ import Column from 'primevue/column'
24
+ import Button from 'primevue/button'
25
+ import Select from 'primevue/select'
26
+ import AutoComplete from 'primevue/autocomplete'
27
+
28
+ const props = defineProps({
29
+ // Header
30
+ title: { type: String, required: true },
31
+ subtitle: { type: String, default: null },
32
+ breadcrumb: { type: Array, default: null },
33
+ headerActions: { type: Array, default: () => [] },
34
+
35
+ // Cards
36
+ cards: { type: Array, default: () => [] },
37
+ cardsColumns: { type: [Number, String], default: 'auto' },
38
+
39
+ // Table data
40
+ items: { type: Array, required: true },
41
+ loading: { type: Boolean, default: false },
42
+ dataKey: { type: String, default: 'id' },
43
+
44
+ // Selection
45
+ selected: { type: Array, default: () => [] },
46
+ selectable: { type: Boolean, default: false },
47
+
48
+ // Pagination
49
+ paginator: { type: Boolean, default: true },
50
+ rows: { type: Number, default: 10 },
51
+ rowsPerPageOptions: { type: Array, default: () => [10, 50, 100] },
52
+ totalRecords: { type: Number, default: 0 },
53
+ lazy: { type: Boolean, default: false },
54
+
55
+ // Sorting
56
+ sortField: { type: String, default: null },
57
+ sortOrder: { type: Number, default: 1 },
58
+
59
+ // Search
60
+ searchQuery: { type: String, default: '' },
61
+ searchPlaceholder: { type: String, default: 'Search...' },
62
+
63
+ // Filters
64
+ filters: { type: Array, default: () => [] },
65
+ filterValues: { type: Object, default: () => ({}) },
66
+
67
+ // Row Actions
68
+ getActions: { type: Function, default: null },
69
+ actionsWidth: { type: String, default: '120px' }
70
+ })
71
+
72
+ function resolveLabel(label, _action) {
73
+ return typeof label === 'function' ? label({ selectionCount: props.selected?.length || 0 }) : label
74
+ }
75
+
76
+ // Local copy of filterValues for proper v-model binding
77
+ // PrimeVue Select needs v-model to fire change events reliably
78
+ const localFilterValues = ref({})
79
+
80
+ // Autocomplete suggestions per filter (keyed by filter name)
81
+ const autocompleteSuggestions = ref({})
82
+
83
+ // For autocomplete filters, we store the selected option object (not just value)
84
+ // to display the label properly. This maps filter.name -> selected option object
85
+ const autocompleteSelected = ref({})
86
+
87
+ // Sync from prop to local (when parent updates)
88
+ watch(() => props.filterValues, (newVal) => {
89
+ localFilterValues.value = { ...newVal }
90
+ // Sync autocomplete selections - find matching option by value
91
+ for (const filter of props.filters) {
92
+ if (filter.type === 'autocomplete' && newVal[filter.name] != null) {
93
+ const option = filter.options?.find(opt =>
94
+ (opt.value ?? opt) === newVal[filter.name]
95
+ )
96
+ if (option) {
97
+ autocompleteSelected.value[filter.name] = option
98
+ }
99
+ } else if (filter.type === 'autocomplete' && newVal[filter.name] == null) {
100
+ autocompleteSelected.value[filter.name] = null
101
+ }
102
+ }
103
+ }, { immediate: true, deep: true })
104
+
105
+ // Check if any filter or search has a non-null value
106
+ const hasActiveFilters = computed(() => {
107
+ const hasFilters = Object.values(props.filterValues).some(v => v !== null && v !== undefined && v !== '')
108
+ const hasSearch = props.searchQuery && props.searchQuery.trim() !== ''
109
+ return hasFilters || hasSearch
110
+ })
111
+
112
+ const emit = defineEmits([
113
+ 'update:selected',
114
+ 'update:searchQuery',
115
+ 'update:filterValues',
116
+ 'page',
117
+ 'sort'
118
+ ])
119
+
120
+ function clearAllFilters() {
121
+ // Build empty filter values object
122
+ const cleared = {}
123
+ for (const key of Object.keys(localFilterValues.value)) {
124
+ cleared[key] = null
125
+ }
126
+ // Update local ref first (for immediate UI feedback)
127
+ localFilterValues.value = cleared
128
+ // Clear autocomplete selections too
129
+ autocompleteSelected.value = {}
130
+ // Then emit to parent
131
+ emit('update:filterValues', cleared)
132
+ // Also clear search
133
+ emit('update:searchQuery', '')
134
+ }
135
+
136
+ function onSelectionChange(value) {
137
+ emit('update:selected', value)
138
+ }
139
+
140
+ function onSearchChange(value) {
141
+ emit('update:searchQuery', value)
142
+ }
143
+
144
+ function onFilterChange(_name) {
145
+ emit('update:filterValues', { ...localFilterValues.value })
146
+ }
147
+
148
+ /**
149
+ * Handle autocomplete search/filter
150
+ * @param {Object} event - PrimeVue autocomplete event with query property
151
+ * @param {Object} filter - Filter definition from props.filters
152
+ */
153
+ function onAutocompleteSearch(event, filter) {
154
+ const query = (event.query || '').toLowerCase()
155
+ const labelField = filter.optionLabel || 'label'
156
+
157
+ if (!query) {
158
+ // Show all options when query is empty
159
+ autocompleteSuggestions.value[filter.name] = [...(filter.options || [])]
160
+ } else {
161
+ // Filter options by label
162
+ autocompleteSuggestions.value[filter.name] = (filter.options || []).filter(opt => {
163
+ const label = typeof opt === 'string' ? opt : (opt[labelField] || '')
164
+ return label.toLowerCase().includes(query)
165
+ })
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Handle autocomplete selection
171
+ * @param {Object} event - PrimeVue event with value property (selected option)
172
+ * @param {Object} filter - Filter definition
173
+ */
174
+ function onAutocompleteSelect(event, filter) {
175
+ const selected = event.value
176
+ const valueField = filter.optionValue || 'value'
177
+
178
+ // Store the selected option for display
179
+ autocompleteSelected.value[filter.name] = selected
180
+
181
+ // Extract the actual value to send to API
182
+ const value = selected != null
183
+ ? (typeof selected === 'string' ? selected : selected[valueField])
184
+ : null
185
+
186
+ localFilterValues.value[filter.name] = value
187
+ emit('update:filterValues', { ...localFilterValues.value })
188
+ }
189
+
190
+ /**
191
+ * Handle autocomplete clear (user clears the input)
192
+ */
193
+ function onAutocompleteClear(filter) {
194
+ autocompleteSelected.value[filter.name] = null
195
+ localFilterValues.value[filter.name] = null
196
+ emit('update:filterValues', { ...localFilterValues.value })
197
+ }
198
+
199
+ function onPage(event) {
200
+ emit('page', event)
201
+ }
202
+
203
+ function onSort(event) {
204
+ emit('sort', event)
205
+ }
206
+ </script>
207
+
208
+ <template>
209
+ <div>
210
+ <PageHeader :title="title" :subtitle="subtitle" :breadcrumb="breadcrumb">
211
+ <template #actions>
212
+ <slot name="header-actions" ></slot>
213
+ <Button
214
+ v-for="action in headerActions"
215
+ :key="action.name"
216
+ :label="resolveLabel(action.label)"
217
+ :icon="action.icon"
218
+ :severity="action.severity"
219
+ :loading="action.isLoading"
220
+ @click="action.onClick"
221
+ />
222
+ </template>
223
+ </PageHeader>
224
+
225
+ <!-- Cards Zone -->
226
+ <CardsGrid :cards="cards" :columns="cardsColumns">
227
+ <template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
228
+ <slot :name="slotName" v-bind="slotProps" ></slot>
229
+ </template>
230
+ </CardsGrid>
231
+
232
+ <!-- Before Table Slot -->
233
+ <slot name="beforeTable" ></slot>
234
+
235
+ <div class="card">
236
+ <!-- Filter Bar -->
237
+ <FilterBar
238
+ :modelValue="searchQuery"
239
+ @update:modelValue="onSearchChange"
240
+ :placeholder="searchPlaceholder"
241
+ >
242
+ <template v-for="filter in filters" :key="filter.name">
243
+ <!-- Autocomplete filter -->
244
+ <AutoComplete
245
+ v-if="filter.type === 'autocomplete'"
246
+ v-model="autocompleteSelected[filter.name]"
247
+ :suggestions="autocompleteSuggestions[filter.name] || []"
248
+ @complete="onAutocompleteSearch($event, filter)"
249
+ @item-select="onAutocompleteSelect($event, filter)"
250
+ @clear="onAutocompleteClear(filter)"
251
+ :optionLabel="filter.optionLabel || 'label'"
252
+ :placeholder="filter.placeholder"
253
+ :dropdown="true"
254
+ :minLength="0"
255
+ :style="{ minWidth: filter.width || '160px' }"
256
+ :class="{ 'filter-active': localFilterValues[filter.name] != null && localFilterValues[filter.name] !== '' }"
257
+ :inputClass="'filter-autocomplete-input'"
258
+ />
259
+ <!-- Standard select filter -->
260
+ <Select
261
+ v-else
262
+ v-model="localFilterValues[filter.name]"
263
+ @update:modelValue="onFilterChange(filter.name)"
264
+ :options="filter.options"
265
+ :optionLabel="filter.optionLabel || 'label'"
266
+ :optionValue="filter.optionValue || 'value'"
267
+ :placeholder="filter.placeholder"
268
+ :style="{ minWidth: filter.width || '160px' }"
269
+ :class="{ 'filter-active': localFilterValues[filter.name] != null && localFilterValues[filter.name] !== '' }"
270
+ />
271
+ </template>
272
+ <slot name="filters" ></slot>
273
+ <Button
274
+ v-if="hasActiveFilters"
275
+ icon="pi pi-filter-slash"
276
+ severity="secondary"
277
+ text
278
+ rounded
279
+ size="small"
280
+ @click="clearAllFilters"
281
+ v-tooltip.top="'Clear filters'"
282
+ />
283
+ </FilterBar>
284
+
285
+ <!-- Data Table -->
286
+ <DataTable
287
+ :value="items"
288
+ :loading="loading"
289
+ :dataKey="dataKey"
290
+ :paginator="paginator"
291
+ :rows="rows"
292
+ :rowsPerPageOptions="rowsPerPageOptions"
293
+ :totalRecords="totalRecords"
294
+ :lazy="lazy"
295
+ :sortField="sortField"
296
+ :sortOrder="sortOrder"
297
+ :selection="selected"
298
+ @update:selection="onSelectionChange"
299
+ @page="onPage"
300
+ @sort="onSort"
301
+ stripedRows
302
+ removableSort
303
+ >
304
+ <!-- Columns from slot -->
305
+ <slot name="columns" ></slot>
306
+
307
+ <!-- Actions column -->
308
+ <Column v-if="getActions" header="Actions" :style="{ width: actionsWidth }">
309
+ <template #body="{ data }">
310
+ <ActionButtons :actions="getActions(data)" />
311
+ </template>
312
+ </Column>
313
+
314
+ <!-- Selection column -->
315
+ <Column v-if="selectable" selectionMode="multiple" headerStyle="width: 3rem" />
316
+ </DataTable>
317
+ </div>
318
+ </div>
319
+ </template>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * qdadm - Composables exports
3
+ */
4
+
5
+ export { useBareForm } from './useBareForm'
6
+ export { useBreadcrumb } from './useBreadcrumb'
7
+ export { useDirtyState } from './useDirtyState'
8
+ export { useEntityTitle } from './useEntityTitle'
9
+ export { useForm } from './useForm'
10
+ export * from './useJsonSyntax'
11
+ export { useListPageBuilder, PAGE_SIZE_OPTIONS } from './useListPageBuilder'
12
+ export { usePageBuilder } from './usePageBuilder'
13
+ export { useSubEditor } from './useSubEditor'
14
+ export { useTabSync } from './useTabSync'
15
+ export { useApp } from './useApp'
16
+ export { useAuth } from './useAuth'
17
+ export { useNavigation } from './useNavigation'
18
+ export { useStatus } from './useStatus'
19
+ export { useUnsavedChangesGuard } from './useUnsavedChangesGuard'
@@ -0,0 +1,43 @@
1
+ /**
2
+ * useApp - Access app branding and configuration
3
+ *
4
+ * Provides access to app config set via createQdadm bootstrap.
5
+ *
6
+ * Usage:
7
+ * const { name, version, logo } = useApp()
8
+ */
9
+
10
+ import { inject, computed } from 'vue'
11
+
12
+ export function useApp() {
13
+ const appConfig = inject('qdadmApp')
14
+
15
+ if (!appConfig) {
16
+ console.warn('[qdadm] qdadmApp not provided. Using defaults.')
17
+ return {
18
+ name: 'Admin',
19
+ shortName: 'Admin',
20
+ logo: null,
21
+ logoSmall: null,
22
+ version: null,
23
+ theme: {}
24
+ }
25
+ }
26
+
27
+ // Version can be string or callback
28
+ const version = computed(() => {
29
+ if (typeof appConfig.version === 'function') {
30
+ return appConfig.version()
31
+ }
32
+ return appConfig.version
33
+ })
34
+
35
+ return {
36
+ name: appConfig.name || 'Admin',
37
+ shortName: appConfig.shortName || appConfig.name || 'Admin',
38
+ logo: appConfig.logo,
39
+ logoSmall: appConfig.logoSmall,
40
+ version,
41
+ theme: appConfig.theme || {}
42
+ }
43
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * useAuth - Access authentication state
3
+ *
4
+ * Provides access to authAdapter set via createQdadm bootstrap.
5
+ * Returns neutral values if auth is disabled.
6
+ *
7
+ * Note: Permission checking (canRead/canWrite) is handled by EntityManager,
8
+ * not by useAuth. This keeps auth simple and delegates permission logic
9
+ * to where it belongs (the entity layer).
10
+ *
11
+ * Usage:
12
+ * const { isAuthenticated, user, logout } = useAuth()
13
+ */
14
+
15
+ import { inject, computed, ref } from 'vue'
16
+
17
+ export function useAuth() {
18
+ const auth = inject('authAdapter')
19
+ const features = inject('qdadmFeatures')
20
+
21
+ // If auth disabled or not provided, return neutral values
22
+ if (!features?.auth || !auth) {
23
+ return {
24
+ login: () => Promise.resolve({ token: null, user: null }),
25
+ logout: () => {},
26
+ getCurrentUser: () => Promise.resolve(null),
27
+ isAuthenticated: computed(() => true), // Always "authenticated"
28
+ user: ref(null),
29
+ authEnabled: false
30
+ }
31
+ }
32
+
33
+ // Reactive user state
34
+ const user = computed(() => {
35
+ if (typeof auth.getUser === 'function') {
36
+ return auth.getUser()
37
+ }
38
+ return auth.user || null
39
+ })
40
+
41
+ return {
42
+ login: auth.login,
43
+ logout: auth.logout,
44
+ getCurrentUser: auth.getCurrentUser,
45
+ isAuthenticated: computed(() => auth.isAuthenticated()),
46
+ user,
47
+ authEnabled: true
48
+ }
49
+ }