@saasicat/ui-vue 0.21.0 → 0.22.1
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/{catalog-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
- package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
- package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
- package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +132 -29
- package/dist/client/index.d.cts +53 -3
- package/dist/client/index.d.ts +53 -3
- package/dist/client/index.js +14 -2
- package/dist/index.cjs +319 -162
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +176 -133
- package/dist/quasar/index.cjs +76 -29
- package/dist/quasar/index.d.cts +2 -2
- package/dist/quasar/index.d.ts +2 -2
- package/dist/quasar/index.js +6 -2
- package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
- package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
- package/dist/{use-super-admin-i18n-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-DlJRGIVq.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
- package/package.json +18 -8
- package/src/client/i18n/messages/bundles.ts +0 -2
- package/src/client/i18n/messages/common.ts +22 -0
- package/src/client/i18n/messages/discovery.ts +2 -2
- package/src/client/i18n/messages/marketing.ts +2 -4
- package/src/client/i18n/messages/plan-detail.ts +0 -4
- package/src/client/i18n/messages/plans.ts +20 -8
- package/src/client/i18n/messages/tenants.ts +0 -2
- package/src/client/index.ts +1 -0
- package/src/client/manifest-loader.ts +1 -1
- package/src/client/resolve-plans.ts +144 -0
- package/src/components/BundleVersionPublishDialog.vue +6 -6
- package/src/components/KvBlock.vue +4 -4
- package/src/components/MarketingPromotionsTab.vue +34 -48
- package/src/components/MfaPromptDialog.vue +8 -2
- package/src/components/TenantActionConfirmDialog.vue +4 -4
- package/src/components/VersionDiffPreview.vue +7 -7
- package/src/components/admin-page/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +22 -5
- package/src/components/admin-page/AdminKpi.vue +84 -0
- package/src/components/admin-page/AdminPaginator.vue +195 -0
- package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
- package/src/components/admin-page/AdminSection.vue +31 -16
- package/src/components/admin-page/AdminStatistics.vue +40 -0
- package/src/components/admin-page/AdminTable.vue +175 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
- package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
- package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
- package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
- package/src/components/dialogs/PilotCreateDialog.vue +12 -12
- package/src/components/dialogs/PilotEditDialog.vue +7 -7
- package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
- package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
- package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
- package/src/components/plan/PlanGrid.vue +1 -1
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
- package/src/components/plan-detail/PlanDetail.vue +92 -302
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-list/PlanList.vue +72 -290
- package/src/components/plan-matrix/PlanMatrix.vue +66 -183
- package/src/components/plan-review/PlanReview.vue +191 -275
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
- package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +34 -10
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +53 -72
- package/src/pages-standard/BundlesPage.vue +128 -144
- package/src/pages-standard/DashboardPage.vue +43 -107
- package/src/pages-standard/DiscoveryPage.vue +158 -200
- package/src/pages-standard/EmailHistoryPage.vue +98 -88
- package/src/pages-standard/MarketingCatalogPage.vue +237 -267
- package/src/pages-standard/PilotsPage.vue +44 -60
- package/src/pages-standard/PlansPage.vue +230 -105
- package/src/pages-standard/PlatformEmailPage.vue +37 -40
- package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
- package/src/pages-standard/PromoCodesPage.vue +86 -112
- package/src/pages-standard/SubscriptionsPage.vue +10 -17
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
- package/src/pages-standard/TenantDetailPage.vue +45 -41
- package/src/pages-standard/TenantsPage.vue +184 -264
- package/src/pages-standard/UsersPage.vue +69 -104
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
- package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
- package/src/pages-standard/bundles-page/BundlesToolbar.vue +6 -10
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +1 -1
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
- package/src/pages-standard/plans-page/PlansPageToast.vue +1 -1
- package/src/pages-standard/sa-theme.css +481 -71
- package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
- package/src/pages-standard/tenants/StatusPill.vue +2 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
- package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/TenantPlanSection.vue +12 -20
- package/src/quasar/create-super-admin-app.ts +4 -0
- package/src/vue/entitlement-provider.ts +0 -1
- package/src/vue/feature-router-guard.ts +0 -1
- package/src/vue/super-admin-context.ts +106 -1
- package/src/vue/use-api-list.ts +2 -1
- package/src/vue/use-pagination.ts +63 -0
- package/src/vue/use-plans.ts +1 -2
- package/src/vue/use-sign-out.ts +90 -0
- package/src/vue/use-tenant-billing.ts +1 -1
- package/src/vue/use-tenant-manifest.ts +0 -1
- package/src/components/KpiCard.vue +0 -53
- package/src/components/plan-cockpit/PlanCockpit.vue +0 -1027
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -249
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
|
@@ -1,84 +1,52 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
2
|
+
<AdminHero :title="title">
|
|
3
|
+
<template #before-title>
|
|
4
|
+
<span class="pve-kicker">
|
|
5
|
+
{{ msg.header.planKicker }}
|
|
6
6
|
<span class="pve-chip pve-chip--plan">{{ planKey }}</span>
|
|
7
|
-
</div>
|
|
8
|
-
<h2 class="pve-title">{{ title }}</h2>
|
|
9
|
-
<span class="pve-chip pve-chip--draft pve-chip--dot">{{ msg.header.draftChip }}</span>
|
|
10
|
-
<span class="pve-bar-note">
|
|
11
|
-
{{ msg.header.supersedeNoteBefore }}
|
|
12
|
-
<code class="pve-mono">{{ predecessorValidUntilHint }}</code>
|
|
13
|
-
{{ msg.header.supersedeNoteAfter }}
|
|
14
7
|
</span>
|
|
15
|
-
</
|
|
16
|
-
<
|
|
8
|
+
</template>
|
|
9
|
+
<template #subtitle>
|
|
10
|
+
<span class="pve-chip pve-chip--draft pve-chip--dot">{{ msg.header.draftChip }}</span>
|
|
11
|
+
{{ msg.header.supersedeNoteBefore }}
|
|
12
|
+
<code class="pve-mono">{{ predecessorValidUntilHint }}</code>
|
|
13
|
+
{{ msg.header.supersedeNoteAfter }}
|
|
14
|
+
</template>
|
|
15
|
+
<template #actions>
|
|
17
16
|
<button
|
|
18
|
-
class="
|
|
17
|
+
class="sa-btn"
|
|
19
18
|
type="button"
|
|
20
19
|
:disabled="!hasPredecessor"
|
|
21
20
|
:title="hasPredecessor ? undefined : msg.header.noPredecessorHint"
|
|
22
21
|
@click="$emit('showDiff')"
|
|
23
22
|
>
|
|
24
|
-
<
|
|
25
|
-
<svg
|
|
26
|
-
width="14"
|
|
27
|
-
height="14"
|
|
28
|
-
viewBox="0 0 24 24"
|
|
29
|
-
fill="none"
|
|
30
|
-
stroke="currentColor"
|
|
31
|
-
stroke-width="2"
|
|
32
|
-
>
|
|
33
|
-
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
34
|
-
<circle cx="12" cy="12" r="3" />
|
|
35
|
-
</svg>
|
|
36
|
-
</span>
|
|
23
|
+
<q-icon name="visibility" size="16px" />
|
|
37
24
|
<span>{{ msg.header.diffButton }}</span>
|
|
38
25
|
</button>
|
|
39
|
-
<button class="
|
|
40
|
-
<
|
|
41
|
-
<svg
|
|
42
|
-
width="14"
|
|
43
|
-
height="14"
|
|
44
|
-
viewBox="0 0 24 24"
|
|
45
|
-
fill="none"
|
|
46
|
-
stroke="currentColor"
|
|
47
|
-
stroke-width="2"
|
|
48
|
-
>
|
|
49
|
-
<path d="M5 12h14M13 5l7 7-7 7" />
|
|
50
|
-
</svg>
|
|
51
|
-
</span>
|
|
26
|
+
<button class="sa-btn" type="button" @click="$emit('cancel')">
|
|
27
|
+
<q-icon name="arrow_back" size="16px" />
|
|
52
28
|
<span>{{ common.back }}</span>
|
|
53
29
|
</button>
|
|
54
30
|
<button
|
|
55
|
-
class="
|
|
31
|
+
class="sa-btn sa-btn--primary"
|
|
56
32
|
type="button"
|
|
57
33
|
:disabled="!canSave"
|
|
58
34
|
@click="$emit('save')"
|
|
59
35
|
>
|
|
60
36
|
<span>{{ msg.header.saveButton }}</span>
|
|
61
|
-
<
|
|
62
|
-
<svg
|
|
63
|
-
width="14"
|
|
64
|
-
height="14"
|
|
65
|
-
viewBox="0 0 24 24"
|
|
66
|
-
fill="none"
|
|
67
|
-
stroke="currentColor"
|
|
68
|
-
stroke-width="2"
|
|
69
|
-
>
|
|
70
|
-
<path d="M5 12h14M13 5l7 7-7 7" />
|
|
71
|
-
</svg>
|
|
72
|
-
</span>
|
|
37
|
+
<q-icon name="arrow_forward" size="16px" />
|
|
73
38
|
</button>
|
|
74
|
-
</
|
|
75
|
-
</
|
|
39
|
+
</template>
|
|
40
|
+
</AdminHero>
|
|
76
41
|
</template>
|
|
77
42
|
|
|
78
43
|
<script setup lang="ts">
|
|
79
44
|
import { computed } from 'vue';
|
|
80
45
|
import { formatMessage } from '../../client/i18n/format.js';
|
|
81
46
|
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
47
|
+
// The editor takes over the page hero while it is open: its actions depend on
|
|
48
|
+
// the draft's validity and on the diff dialog, both of which live in this view.
|
|
49
|
+
import AdminHero from '../admin-page/AdminHero.vue';
|
|
82
50
|
|
|
83
51
|
const props = defineProps<{
|
|
84
52
|
planKey: string;
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from './vue/use-super-admin-i18n.js';
|
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
40
|
export * from './vue/create-admin-routes.js';
|
|
41
41
|
export * from './vue/use-api-list.js';
|
|
42
|
+
export * from './vue/use-pagination.js';
|
|
42
43
|
export * from './vue/use-tenants.js';
|
|
43
44
|
export * from './vue/use-audit-entries.js';
|
|
44
45
|
export * from './vue/use-entitlement.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<q-layout view="lHh Lpr lFf">
|
|
2
|
+
<q-layout view="lHh Lpr lFf" class="sa-admin-layout">
|
|
3
3
|
<div v-if="isProduction" class="sa-admin-banner sa-admin-banner--prod">
|
|
4
4
|
<q-icon name="warning" size="14px" />
|
|
5
5
|
<strong>PRODUCTION</strong> — {{ msg.header.productionWarning }}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<div class="text-body2 text-weight-semibold">{{ userName }}</div>
|
|
25
25
|
<div class="text-caption sa-admin-user__email">{{ userEmail }}</div>
|
|
26
26
|
</div>
|
|
27
|
-
<q-btn flat round dense icon="logout" size="sm" @click="
|
|
27
|
+
<q-btn flat round dense icon="logout" size="sm" @click="onLogoutClick">
|
|
28
28
|
<q-tooltip>{{ msg.header.logout }}</q-tooltip>
|
|
29
29
|
</q-btn>
|
|
30
30
|
</div>
|
|
@@ -96,13 +96,14 @@
|
|
|
96
96
|
</template>
|
|
97
97
|
|
|
98
98
|
<script setup lang="ts">
|
|
99
|
-
import { computed, inject, ref } from 'vue';
|
|
99
|
+
import { computed, inject, ref, getCurrentInstance } from 'vue';
|
|
100
100
|
import { useRoute } from 'vue-router';
|
|
101
101
|
import type { AdminManifest, StandardPageKey } from '@saasicat/types';
|
|
102
102
|
import { buildRoutes, buildSidebar, defaultSectionOrder } from '../client/nav-builder.js';
|
|
103
103
|
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
|
104
104
|
import { SUPER_ADMIN_BRAND_KEY, SUPER_ADMIN_MANIFEST_KEY } from '../vue/super-admin-context.js';
|
|
105
105
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
106
|
+
import { useSignOut } from '../vue/use-sign-out.js';
|
|
106
107
|
import { ADMIN_UI_VERSION } from '../client/version.js';
|
|
107
108
|
|
|
108
109
|
// SuperAdmin layout — universal platform shell for all consumer apps.
|
|
@@ -189,6 +190,29 @@ const emit = defineEmits<{
|
|
|
189
190
|
(e: 'logout'): void;
|
|
190
191
|
}>();
|
|
191
192
|
|
|
193
|
+
const signOut = useSignOut();
|
|
194
|
+
const instance = getCurrentInstance();
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Signs out, and only emits instead when the app is actually listening.
|
|
198
|
+
*
|
|
199
|
+
* The layout is mounted as a plain route record in every consumer we have
|
|
200
|
+
* (`{ path: '/admin', component: AdminLayout }`), and a route record attaches
|
|
201
|
+
* no listeners — so `emit('logout')` alone made the header's sign-out button
|
|
202
|
+
* do nothing at all. Apps that pass `@logout` keep full control.
|
|
203
|
+
*/
|
|
204
|
+
function onLogoutClick(): void | Promise<void> {
|
|
205
|
+
// Both spellings: `@logout` lands on `onLogout`, `@logout.once` on
|
|
206
|
+
// `onLogoutOnce`. Checking only the first would run the platform default
|
|
207
|
+
// and never emit — silently ignoring a listener the consumer did attach.
|
|
208
|
+
const listeners = instance?.vnode.props ?? {};
|
|
209
|
+
if (listeners.onLogout || listeners.onLogoutOnce) {
|
|
210
|
+
emit('logout');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
return signOut();
|
|
214
|
+
}
|
|
215
|
+
|
|
192
216
|
const route = useRoute();
|
|
193
217
|
const msg = useSaMessages('shell');
|
|
194
218
|
const { messages } = useSuperAdminI18n();
|
|
@@ -295,7 +319,7 @@ const currentPageTitle = computed(() => {
|
|
|
295
319
|
}
|
|
296
320
|
|
|
297
321
|
.sa-admin-header {
|
|
298
|
-
background: var(--sa-admin-header-bg, linear-gradient(90deg,
|
|
322
|
+
background: var(--sa-admin-header-bg, linear-gradient(90deg, var(--sa-heading), #1e293b));
|
|
299
323
|
color: var(--sa-admin-header-fg, #fff);
|
|
300
324
|
}
|
|
301
325
|
.sa-admin-header__sub {
|
|
@@ -306,7 +330,7 @@ const currentPageTitle = computed(() => {
|
|
|
306
330
|
|
|
307
331
|
.sa-admin-badge {
|
|
308
332
|
background: var(--sa-admin-badge-bg, #f59e0b);
|
|
309
|
-
color: var(--sa-admin-badge-fg,
|
|
333
|
+
color: var(--sa-admin-badge-fg, var(--sa-heading));
|
|
310
334
|
font-weight: 800;
|
|
311
335
|
letter-spacing: 0.08em;
|
|
312
336
|
font-size: 10px;
|
|
@@ -333,7 +357,7 @@ const currentPageTitle = computed(() => {
|
|
|
333
357
|
.sa-admin-drawer :deep(.q-drawer__content),
|
|
334
358
|
.sa-admin-drawer__stack {
|
|
335
359
|
background: var(--sa-admin-drawer-bg, #0b1220);
|
|
336
|
-
color: var(--sa-admin-drawer-fg,
|
|
360
|
+
color: var(--sa-admin-drawer-fg, var(--sa-border-soft));
|
|
337
361
|
}
|
|
338
362
|
.sa-admin-drawer__stack {
|
|
339
363
|
display: flex;
|
|
@@ -353,7 +377,7 @@ const currentPageTitle = computed(() => {
|
|
|
353
377
|
height: 36px;
|
|
354
378
|
border-radius: 10px;
|
|
355
379
|
background: var(--sa-admin-drawer-logo-bg, linear-gradient(135deg, #f59e0b, #d97706));
|
|
356
|
-
color: var(--sa-admin-drawer-logo-fg,
|
|
380
|
+
color: var(--sa-admin-drawer-logo-fg, var(--sa-heading));
|
|
357
381
|
font-weight: 800;
|
|
358
382
|
display: flex;
|
|
359
383
|
align-items: center;
|
|
@@ -378,7 +402,7 @@ const currentPageTitle = computed(() => {
|
|
|
378
402
|
font-size: 10px;
|
|
379
403
|
text-transform: uppercase;
|
|
380
404
|
letter-spacing: 0.12em;
|
|
381
|
-
color: var(--sa-admin-drawer-section-fg,
|
|
405
|
+
color: var(--sa-admin-drawer-section-fg, var(--sa-muted));
|
|
382
406
|
padding: 14px 18px 6px;
|
|
383
407
|
font-weight: 600;
|
|
384
408
|
}
|
|
@@ -396,7 +420,7 @@ const currentPageTitle = computed(() => {
|
|
|
396
420
|
padding-right: 8px;
|
|
397
421
|
}
|
|
398
422
|
.sa-admin-drawer__list :deep(.q-item .q-icon) {
|
|
399
|
-
color:
|
|
423
|
+
color: var(--sa-border);
|
|
400
424
|
}
|
|
401
425
|
.sa-admin-drawer__list :deep(.q-item:hover) {
|
|
402
426
|
background: var(--sa-admin-drawer-hover-bg, rgba(245, 158, 11, 0.15));
|
|
@@ -425,7 +449,7 @@ const currentPageTitle = computed(() => {
|
|
|
425
449
|
align-items: center;
|
|
426
450
|
gap: 6px;
|
|
427
451
|
margin-top: 6px;
|
|
428
|
-
color:
|
|
452
|
+
color: var(--sa-border);
|
|
429
453
|
text-decoration: none;
|
|
430
454
|
}
|
|
431
455
|
.sa-admin-drawer__doc:hover {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<strong>{{ msg.manifestError.detailLabel }}</strong> {{ errorMessage }}
|
|
12
12
|
</p>
|
|
13
13
|
<div class="sa-manifest-error__actions">
|
|
14
|
-
<q-btn unelevated color="primary" :label="common.reload" @click="
|
|
15
|
-
<q-btn flat :label="msg.header.logout" @click="
|
|
14
|
+
<q-btn unelevated color="primary" :label="common.reload" @click="retry" />
|
|
15
|
+
<q-btn flat :label="msg.header.logout" @click="logout" />
|
|
16
16
|
</div>
|
|
17
17
|
</q-card-section>
|
|
18
18
|
</q-card>
|
|
@@ -20,21 +20,91 @@
|
|
|
20
20
|
</template>
|
|
21
21
|
|
|
22
22
|
<script setup lang="ts">
|
|
23
|
+
import { inject } from 'vue';
|
|
24
|
+
import { useRouter } from 'vue-router';
|
|
23
25
|
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
26
|
+
import { useSignOut } from '../vue/use-sign-out.js';
|
|
27
|
+
import { SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY } from '../vue/super-admin-context.js';
|
|
24
28
|
|
|
25
29
|
// Platform standard error page for `manifestGuard.errorRoute`.
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
30
|
+
//
|
|
31
|
+
// This page is mounted BY THE ROUTER (`createAdminRoutes({ adminErrorPage })`),
|
|
32
|
+
// and a route record passes no props. Every prop here must therefore be
|
|
33
|
+
// optional with a default that works standalone — a required prop would be
|
|
34
|
+
// unsatisfiable by construction, and its two buttons would call `undefined`
|
|
35
|
+
// at the exact moment the app is already in trouble.
|
|
36
|
+
//
|
|
37
|
+
// Apps that want app-specific behaviour (clear the manifest cache, call
|
|
38
|
+
// authStore.logout, route somewhere else) mount the page themselves with
|
|
39
|
+
// `props:` on the route record and override the callbacks.
|
|
29
40
|
|
|
30
41
|
const msg = useSaMessages('shell');
|
|
31
42
|
const common = useSaMessages('common');
|
|
43
|
+
const router = useRouter();
|
|
32
44
|
|
|
33
|
-
defineProps<{
|
|
45
|
+
const props = defineProps<{
|
|
34
46
|
errorMessage?: string | null;
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
/** Default: fresh boot into `retryPath`, which re-runs the manifest guard. */
|
|
48
|
+
onRetry?: () => Promise<void> | void;
|
|
49
|
+
/** Default: navigate to the login route `createAdminRoutes` registers. */
|
|
50
|
+
onLogout?: () => void | Promise<void>;
|
|
51
|
+
/** Login path for the default `onLogout`. */
|
|
52
|
+
loginPath?: string;
|
|
53
|
+
/** Guarded route the default `onRetry` boots into. */
|
|
54
|
+
retryPath?: string;
|
|
37
55
|
}>();
|
|
56
|
+
|
|
57
|
+
const signOut = useSignOut({ loginPath: props.loginPath });
|
|
58
|
+
|
|
59
|
+
// Provided by the shell when the app passes `manifestGuard.clearCache`. The
|
|
60
|
+
// page is route-mounted, so injection is the only route in.
|
|
61
|
+
const clearManifestCache = inject(SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY, undefined);
|
|
62
|
+
|
|
63
|
+
// Both handlers RETURN their promise rather than discarding it with `void`.
|
|
64
|
+
// Vue's event invoker forwards a returned rejection to the app's error handler;
|
|
65
|
+
// a dropped one becomes an unhandled rejection instead — and this is the page
|
|
66
|
+
// that renders when things are already going wrong, so a consumer's failing
|
|
67
|
+
// `onRetry` would vanish exactly where it matters most.
|
|
68
|
+
function retry(): void | Promise<void> {
|
|
69
|
+
if (props.onRetry) return props.onRetry();
|
|
70
|
+
return bootIntoGuardedRoute();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Discards the cached manifest, then boots into a route the guard protects.
|
|
75
|
+
*
|
|
76
|
+
* Two things have to happen, and each covers a failure the other does not:
|
|
77
|
+
*
|
|
78
|
+
* The target must be GUARDED. `createAdminRoutes` registers `/admin-error` as
|
|
79
|
+
* `meta.public` — it has to be, or the guard would redirect to itself forever
|
|
80
|
+
* — and the guard returns before it ever reaches `ensureLoaded()` on a public
|
|
81
|
+
* route. Reloading in place therefore re-renders the same error even after the
|
|
82
|
+
* backend has recovered.
|
|
83
|
+
*
|
|
84
|
+
* And the cache must go first. The loader keeps an ETag in storage, which a
|
|
85
|
+
* full document load does not touch. Its own documented failure — "server
|
|
86
|
+
* returned 304 but the cache body is missing" — would otherwise repeat exactly:
|
|
87
|
+
* same `If-None-Match`, same 304, same missing body, straight back here.
|
|
88
|
+
*
|
|
89
|
+
* `router.resolve` rather than a bare path so an app served under a base href
|
|
90
|
+
* lands inside its own app and not at the domain root.
|
|
91
|
+
*/
|
|
92
|
+
async function bootIntoGuardedRoute(): Promise<void> {
|
|
93
|
+
if (clearManifestCache) {
|
|
94
|
+
try {
|
|
95
|
+
await clearManifestCache();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
// Retrying with a stale cache is still better than not retrying.
|
|
98
|
+
console.warn('[SaaSiCat] Clearing the manifest cache failed before retry:', err);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
window.location.assign(router.resolve(props.retryPath ?? '/admin').href);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function logout(): void | Promise<void> {
|
|
105
|
+
if (props.onLogout) return props.onLogout();
|
|
106
|
+
return signOut();
|
|
107
|
+
}
|
|
38
108
|
</script>
|
|
39
109
|
|
|
40
110
|
<style scoped>
|
|
@@ -59,7 +129,7 @@ defineProps<{
|
|
|
59
129
|
.sa-manifest-error__lead,
|
|
60
130
|
.sa-manifest-error__detail {
|
|
61
131
|
margin: 0 0 12px;
|
|
62
|
-
color:
|
|
132
|
+
color: var(--sa-muted-dark);
|
|
63
133
|
line-height: 1.6;
|
|
64
134
|
}
|
|
65
135
|
.sa-manifest-error__detail {
|
|
@@ -5,59 +5,52 @@
|
|
|
5
5
|
:subtitle="formatMessage(msg.subtitle, { count: rows.length })"
|
|
6
6
|
>
|
|
7
7
|
<template #actions>
|
|
8
|
-
<
|
|
9
|
-
unelevated
|
|
10
|
-
color="primary"
|
|
11
|
-
icon="refresh"
|
|
12
|
-
:label="msg.filterButton"
|
|
13
|
-
@click="reload"
|
|
14
|
-
/>
|
|
8
|
+
<AdminRefreshBtn :loading="loading" @refresh="reload" />
|
|
15
9
|
</template>
|
|
16
10
|
</AdminHero>
|
|
17
11
|
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
<q-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
>
|
|
12
|
+
<AdminBody>
|
|
13
|
+
<AdminSection class="sa-audit__card">
|
|
14
|
+
<AdminFilters class="q-mb-lg">
|
|
15
|
+
<q-input
|
|
16
|
+
v-model="filter.actor"
|
|
17
|
+
outlined
|
|
18
|
+
dense
|
|
19
|
+
:label="msg.filters.actor"
|
|
20
|
+
clearable
|
|
21
|
+
debounce="250"
|
|
22
|
+
@update:model-value="reload"
|
|
23
|
+
/>
|
|
24
|
+
<q-input
|
|
25
|
+
v-model="filter.action"
|
|
26
|
+
outlined
|
|
27
|
+
dense
|
|
28
|
+
:label="msg.filters.action"
|
|
29
|
+
clearable
|
|
30
|
+
debounce="250"
|
|
31
|
+
@update:model-value="reload"
|
|
32
|
+
/>
|
|
33
|
+
<q-input
|
|
34
|
+
v-model="filter.entity"
|
|
35
|
+
outlined
|
|
36
|
+
dense
|
|
37
|
+
:label="msg.filters.entity"
|
|
38
|
+
clearable
|
|
39
|
+
debounce="250"
|
|
40
|
+
@update:model-value="reload"
|
|
41
|
+
/>
|
|
42
|
+
<q-input
|
|
43
|
+
v-model="filter.since"
|
|
44
|
+
outlined
|
|
45
|
+
dense
|
|
46
|
+
type="date"
|
|
47
|
+
:label="msg.filters.since"
|
|
48
|
+
clearable
|
|
49
|
+
@update:model-value="reload"
|
|
50
|
+
/>
|
|
51
|
+
</AdminFilters>
|
|
52
|
+
|
|
53
|
+
<AdminTable :rows="rows" :columns="columns" :loading="loading" storage-key="audit">
|
|
61
54
|
<template #body-cell-changes="{ row }">
|
|
62
55
|
<q-td>
|
|
63
56
|
<q-btn flat dense icon="code" color="primary" @click="openDetail(row)">
|
|
@@ -65,9 +58,9 @@
|
|
|
65
58
|
</q-btn>
|
|
66
59
|
</q-td>
|
|
67
60
|
</template>
|
|
68
|
-
</
|
|
69
|
-
</
|
|
70
|
-
</
|
|
61
|
+
</AdminTable>
|
|
62
|
+
</AdminSection>
|
|
63
|
+
</AdminBody>
|
|
71
64
|
|
|
72
65
|
<q-dialog v-model="detailOpen">
|
|
73
66
|
<q-card style="min-width: 480px; max-width: 96vw">
|
|
@@ -92,8 +85,13 @@
|
|
|
92
85
|
</template>
|
|
93
86
|
|
|
94
87
|
<script setup lang="ts">
|
|
88
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
95
89
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
90
|
+
import AdminRefreshBtn from '../components/admin-page/AdminRefreshBtn.vue';
|
|
91
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
92
|
+
import AdminFilters from '../components/admin-page/AdminFilters.vue';
|
|
96
93
|
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
94
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
97
95
|
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
98
96
|
import { formatMessage } from '../client/i18n/format.js';
|
|
99
97
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
@@ -207,26 +205,9 @@ function formatTs(iso: string | null | undefined): string {
|
|
|
207
205
|
</script>
|
|
208
206
|
|
|
209
207
|
<style scoped>
|
|
210
|
-
.sa-audit__filter {
|
|
211
|
-
display: flex;
|
|
212
|
-
gap: 10px;
|
|
213
|
-
margin-bottom: 12px;
|
|
214
|
-
flex-wrap: wrap;
|
|
215
|
-
}
|
|
216
|
-
.sa-audit__filter > * {
|
|
217
|
-
flex: 1;
|
|
218
|
-
min-width: 180px;
|
|
219
|
-
}
|
|
220
|
-
.sa-audit__card {
|
|
221
|
-
background: #fff;
|
|
222
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
223
|
-
border-radius: 12px;
|
|
224
|
-
overflow: hidden;
|
|
225
|
-
padding: 8px 0;
|
|
226
|
-
}
|
|
227
208
|
.sa-audit__kv {
|
|
228
|
-
background:
|
|
229
|
-
border: 1px solid var(--sa-border,
|
|
209
|
+
background: var(--sa-bg-surface-2);
|
|
210
|
+
border: 1px solid var(--sa-border, var(--sa-border));
|
|
230
211
|
border-radius: 8px;
|
|
231
212
|
padding: 12px;
|
|
232
213
|
font-size: 12px;
|