@saasicat/ui-vue 0.19.0 → 0.22.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.
- package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
- package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
- package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
- package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +134 -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 +321 -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 +78 -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-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-1nCOkvgi.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 +24 -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/AdminBody.vue +40 -0
- package/src/components/admin-page/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +49 -0
- package/src/components/admin-page/AdminKpi.vue +84 -0
- package/src/components/admin-page/AdminPage.vue +16 -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 +53 -0
- 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/PlanAuditLog.vue +7 -8
- package/src/components/plan-detail/PlanDetail.vue +95 -292
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
- package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
- package/src/components/plan-list/PlanList.vue +206 -457
- 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 +38 -12
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +65 -109
- package/src/pages-standard/BundlesPage.vue +146 -179
- package/src/pages-standard/DashboardPage.vue +59 -154
- package/src/pages-standard/DiscoveryPage.vue +172 -227
- package/src/pages-standard/EmailHistoryPage.vue +118 -159
- package/src/pages-standard/MarketingCatalogPage.vue +399 -440
- package/src/pages-standard/PilotsPage.vue +69 -117
- package/src/pages-standard/PlansPage.vue +240 -112
- package/src/pages-standard/PlatformEmailPage.vue +56 -91
- package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
- package/src/pages-standard/PromoCodesPage.vue +95 -128
- package/src/pages-standard/SubscriptionsPage.vue +19 -52
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
- package/src/pages-standard/TenantDetailPage.vue +100 -191
- package/src/pages-standard/TenantsPage.vue +190 -295
- package/src/pages-standard/UsersPage.vue +89 -157
- 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 +63 -0
- 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 +111 -73
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
- 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 +6 -6
- package/src/pages-standard/sa-theme.css +552 -86
- package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
- package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
- package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
- package/src/pages-standard/tenant-detail/types.ts +38 -0
- 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-mfa-prompt.ts +72 -0
- 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 -1012
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
- 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 -251
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
- package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
|
@@ -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>
|
|
@@ -86,21 +86,24 @@
|
|
|
86
86
|
</q-drawer>
|
|
87
87
|
|
|
88
88
|
<q-page-container>
|
|
89
|
-
|
|
89
|
+
<!-- The document's only <main>. Pages must not add one of their
|
|
90
|
+
own, and must not use QPage, which renders <main> itself. -->
|
|
91
|
+
<main :class="['sa-admin-content', { 'sa-admin-content--fullbleed': isFullbleed }]">
|
|
90
92
|
<router-view />
|
|
91
|
-
</
|
|
93
|
+
</main>
|
|
92
94
|
</q-page-container>
|
|
93
95
|
</q-layout>
|
|
94
96
|
</template>
|
|
95
97
|
|
|
96
98
|
<script setup lang="ts">
|
|
97
|
-
import { computed, inject, ref } from 'vue';
|
|
99
|
+
import { computed, inject, ref, getCurrentInstance } from 'vue';
|
|
98
100
|
import { useRoute } from 'vue-router';
|
|
99
101
|
import type { AdminManifest, StandardPageKey } from '@saasicat/types';
|
|
100
102
|
import { buildRoutes, buildSidebar, defaultSectionOrder } from '../client/nav-builder.js';
|
|
101
103
|
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
|
102
104
|
import { SUPER_ADMIN_BRAND_KEY, SUPER_ADMIN_MANIFEST_KEY } from '../vue/super-admin-context.js';
|
|
103
105
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
106
|
+
import { useSignOut } from '../vue/use-sign-out.js';
|
|
104
107
|
import { ADMIN_UI_VERSION } from '../client/version.js';
|
|
105
108
|
|
|
106
109
|
// SuperAdmin layout — universal platform shell for all consumer apps.
|
|
@@ -187,6 +190,29 @@ const emit = defineEmits<{
|
|
|
187
190
|
(e: 'logout'): void;
|
|
188
191
|
}>();
|
|
189
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
|
+
|
|
190
216
|
const route = useRoute();
|
|
191
217
|
const msg = useSaMessages('shell');
|
|
192
218
|
const { messages } = useSuperAdminI18n();
|
|
@@ -293,7 +319,7 @@ const currentPageTitle = computed(() => {
|
|
|
293
319
|
}
|
|
294
320
|
|
|
295
321
|
.sa-admin-header {
|
|
296
|
-
background: var(--sa-admin-header-bg, linear-gradient(90deg,
|
|
322
|
+
background: var(--sa-admin-header-bg, linear-gradient(90deg, var(--sa-heading), #1e293b));
|
|
297
323
|
color: var(--sa-admin-header-fg, #fff);
|
|
298
324
|
}
|
|
299
325
|
.sa-admin-header__sub {
|
|
@@ -304,7 +330,7 @@ const currentPageTitle = computed(() => {
|
|
|
304
330
|
|
|
305
331
|
.sa-admin-badge {
|
|
306
332
|
background: var(--sa-admin-badge-bg, #f59e0b);
|
|
307
|
-
color: var(--sa-admin-badge-fg,
|
|
333
|
+
color: var(--sa-admin-badge-fg, var(--sa-heading));
|
|
308
334
|
font-weight: 800;
|
|
309
335
|
letter-spacing: 0.08em;
|
|
310
336
|
font-size: 10px;
|
|
@@ -331,7 +357,7 @@ const currentPageTitle = computed(() => {
|
|
|
331
357
|
.sa-admin-drawer :deep(.q-drawer__content),
|
|
332
358
|
.sa-admin-drawer__stack {
|
|
333
359
|
background: var(--sa-admin-drawer-bg, #0b1220);
|
|
334
|
-
color: var(--sa-admin-drawer-fg,
|
|
360
|
+
color: var(--sa-admin-drawer-fg, var(--sa-border-soft));
|
|
335
361
|
}
|
|
336
362
|
.sa-admin-drawer__stack {
|
|
337
363
|
display: flex;
|
|
@@ -351,7 +377,7 @@ const currentPageTitle = computed(() => {
|
|
|
351
377
|
height: 36px;
|
|
352
378
|
border-radius: 10px;
|
|
353
379
|
background: var(--sa-admin-drawer-logo-bg, linear-gradient(135deg, #f59e0b, #d97706));
|
|
354
|
-
color: var(--sa-admin-drawer-logo-fg,
|
|
380
|
+
color: var(--sa-admin-drawer-logo-fg, var(--sa-heading));
|
|
355
381
|
font-weight: 800;
|
|
356
382
|
display: flex;
|
|
357
383
|
align-items: center;
|
|
@@ -376,7 +402,7 @@ const currentPageTitle = computed(() => {
|
|
|
376
402
|
font-size: 10px;
|
|
377
403
|
text-transform: uppercase;
|
|
378
404
|
letter-spacing: 0.12em;
|
|
379
|
-
color: var(--sa-admin-drawer-section-fg,
|
|
405
|
+
color: var(--sa-admin-drawer-section-fg, var(--sa-muted));
|
|
380
406
|
padding: 14px 18px 6px;
|
|
381
407
|
font-weight: 600;
|
|
382
408
|
}
|
|
@@ -394,7 +420,7 @@ const currentPageTitle = computed(() => {
|
|
|
394
420
|
padding-right: 8px;
|
|
395
421
|
}
|
|
396
422
|
.sa-admin-drawer__list :deep(.q-item .q-icon) {
|
|
397
|
-
color:
|
|
423
|
+
color: var(--sa-border);
|
|
398
424
|
}
|
|
399
425
|
.sa-admin-drawer__list :deep(.q-item:hover) {
|
|
400
426
|
background: var(--sa-admin-drawer-hover-bg, rgba(245, 158, 11, 0.15));
|
|
@@ -423,7 +449,7 @@ const currentPageTitle = computed(() => {
|
|
|
423
449
|
align-items: center;
|
|
424
450
|
gap: 6px;
|
|
425
451
|
margin-top: 6px;
|
|
426
|
-
color:
|
|
452
|
+
color: var(--sa-border);
|
|
427
453
|
text-decoration: none;
|
|
428
454
|
}
|
|
429
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 {
|
|
@@ -1,64 +1,56 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
</
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
flat
|
|
55
|
-
:rows="rows"
|
|
56
|
-
:columns="columns"
|
|
57
|
-
row-key="id"
|
|
58
|
-
:pagination="{ rowsPerPage: 0 }"
|
|
59
|
-
:loading="loading"
|
|
60
|
-
hide-pagination
|
|
61
|
-
>
|
|
2
|
+
<AdminPage class="sa-audit">
|
|
3
|
+
<AdminHero
|
|
4
|
+
:title="msg.title"
|
|
5
|
+
:subtitle="formatMessage(msg.subtitle, { count: rows.length })"
|
|
6
|
+
>
|
|
7
|
+
<template #actions>
|
|
8
|
+
<AdminRefreshBtn :loading="loading" @refresh="reload" />
|
|
9
|
+
</template>
|
|
10
|
+
</AdminHero>
|
|
11
|
+
|
|
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">
|
|
62
54
|
<template #body-cell-changes="{ row }">
|
|
63
55
|
<q-td>
|
|
64
56
|
<q-btn flat dense icon="code" color="primary" @click="openDetail(row)">
|
|
@@ -66,9 +58,9 @@
|
|
|
66
58
|
</q-btn>
|
|
67
59
|
</q-td>
|
|
68
60
|
</template>
|
|
69
|
-
</
|
|
70
|
-
</
|
|
71
|
-
</
|
|
61
|
+
</AdminTable>
|
|
62
|
+
</AdminSection>
|
|
63
|
+
</AdminBody>
|
|
72
64
|
|
|
73
65
|
<q-dialog v-model="detailOpen">
|
|
74
66
|
<q-card style="min-width: 480px; max-width: 96vw">
|
|
@@ -89,11 +81,18 @@
|
|
|
89
81
|
</q-card-actions>
|
|
90
82
|
</q-card>
|
|
91
83
|
</q-dialog>
|
|
92
|
-
</
|
|
84
|
+
</AdminPage>
|
|
93
85
|
</template>
|
|
94
86
|
|
|
95
87
|
<script setup lang="ts">
|
|
88
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
96
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';
|
|
93
|
+
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
94
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
95
|
+
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
97
96
|
import { formatMessage } from '../client/i18n/format.js';
|
|
98
97
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
99
98
|
|
|
@@ -206,52 +205,9 @@ function formatTs(iso: string | null | undefined): string {
|
|
|
206
205
|
</script>
|
|
207
206
|
|
|
208
207
|
<style scoped>
|
|
209
|
-
.sa-audit {
|
|
210
|
-
min-height: calc(100vh - 56px);
|
|
211
|
-
background: var(--sa-bg-app, #f1f5f9);
|
|
212
|
-
}
|
|
213
|
-
.sa-audit__head {
|
|
214
|
-
padding: 20px 28px 8px;
|
|
215
|
-
display: flex;
|
|
216
|
-
align-items: flex-start;
|
|
217
|
-
justify-content: space-between;
|
|
218
|
-
gap: 16px;
|
|
219
|
-
}
|
|
220
|
-
.sa-audit__title {
|
|
221
|
-
margin: 0;
|
|
222
|
-
font-family: var(--sa-font-head, system-ui, sans-serif);
|
|
223
|
-
font-weight: 700;
|
|
224
|
-
font-size: 22px;
|
|
225
|
-
color: var(--sa-heading, #0f172a);
|
|
226
|
-
}
|
|
227
|
-
.sa-audit__sub {
|
|
228
|
-
margin: 4px 0 0;
|
|
229
|
-
color: var(--sa-muted-dark, #475569);
|
|
230
|
-
font-size: 13.5px;
|
|
231
|
-
}
|
|
232
|
-
.sa-audit__body {
|
|
233
|
-
padding: 12px 28px 28px;
|
|
234
|
-
}
|
|
235
|
-
.sa-audit__filter {
|
|
236
|
-
display: flex;
|
|
237
|
-
gap: 10px;
|
|
238
|
-
margin-bottom: 12px;
|
|
239
|
-
flex-wrap: wrap;
|
|
240
|
-
}
|
|
241
|
-
.sa-audit__filter > * {
|
|
242
|
-
flex: 1;
|
|
243
|
-
min-width: 180px;
|
|
244
|
-
}
|
|
245
|
-
.sa-audit__card {
|
|
246
|
-
background: #fff;
|
|
247
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
248
|
-
border-radius: 12px;
|
|
249
|
-
overflow: hidden;
|
|
250
|
-
padding: 8px 0;
|
|
251
|
-
}
|
|
252
208
|
.sa-audit__kv {
|
|
253
|
-
background:
|
|
254
|
-
border: 1px solid var(--sa-border,
|
|
209
|
+
background: var(--sa-bg-surface-2);
|
|
210
|
+
border: 1px solid var(--sa-border, var(--sa-border));
|
|
255
211
|
border-radius: 8px;
|
|
256
212
|
padding: 12px;
|
|
257
213
|
font-size: 12px;
|