@navette-js/panels 1.0.0-beta.4 → 1.0.0-beta.5
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/THIRD-PARTY-NOTICES.md +1 -0
- package/dist/navette/navette/calendar.css +5 -5
- package/dist/navette/navette/motion.css +21 -21
- package/dist/navette/navette/motion.js +6 -6
- package/dist/navette/navette/security-keys.css +11 -0
- package/dist/navette/navette/security-keys.js +72 -0
- package/lib/auth.d.ts +17 -0
- package/lib/auth.js +70 -0
- package/lib/error-pages.d.ts +9 -0
- package/lib/error-pages.js +173 -0
- package/lib/import-export.d.ts +45 -2
- package/lib/import-export.js +235 -62
- package/lib/index.d.ts +447 -74
- package/lib/index.js +1280 -267
- package/lib/multi-factor.d.ts +348 -0
- package/lib/multi-factor.js +928 -0
- package/lib/qr-code.d.ts +16 -0
- package/lib/qr-code.js +255 -0
- package/lib/throttle.d.ts +10 -0
- package/lib/throttle.js +25 -0
- package/lib/topbar.d.ts +3 -1
- package/lib/topbar.js +18 -1
- package/lib/totp.d.ts +38 -0
- package/lib/totp.js +104 -0
- package/lib/webauthn.d.ts +155 -0
- package/lib/webauthn.js +287 -0
- package/lib/xlsx.d.ts +5 -0
- package/lib/xlsx.js +103 -1
- package/package.json +9 -9
- package/views/components/tenant-menu.ejs +3 -3
- package/views/errors/page.ejs +87 -0
- package/views/layout/base.ejs +2 -0
- package/views/livewire/sidebar.ejs +29 -15
- package/views/livewire/topbar.ejs +1 -1
- package/views/pages/page.ejs +53 -35
- package/views/pages/simple.ejs +4 -0
package/lib/index.d.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
export { Throttle } from './throttle.ts';
|
|
1
2
|
import { type Mailer, type MailMessage } from './auth.ts';
|
|
2
|
-
export { consoleMailer, fileMailer, hashPassword, knexAuthProvider, knexTenantProvider, mailText, verifyPassword, type Mailer, type MailMessage, type TableQuery } from './auth.ts';
|
|
3
|
+
export { consoleMailer, fileMailer, hashPassword, knexAuthProvider, knexTenantProvider, mailText, multiFactorAuthenticationSchema, verifyPassword, type Mailer, type MailMessage, type TableQuery } from './auth.ts';
|
|
3
4
|
import { type Request, type Response, type Router } from 'express';
|
|
4
5
|
import { Action } from '@navette-js/actions';
|
|
5
6
|
import { Step, Wizard } from '@navette-js/forms';
|
|
6
7
|
import { Component as SchemaComponent, Schema, type SchemaHost } from '@navette-js/schemas';
|
|
7
8
|
export { Notification, Notifications } from './notifications.ts';
|
|
9
|
+
import { MultiFactorChallenge, type MultiFactorAuthenticationProvider, type SecurityKey } from './multi-factor.ts';
|
|
10
|
+
export { AppAuthentication, EmailAuthentication, EmailCodeInput, type EmailAuthenticationStore, decryptSecret, encryptSecret, MemoryCacheStore, MultiFactorChallenge, SecurityKeyAuthentication, SecurityKeyInput, useMultiFactorCacheStore, type AppAuthenticationStore, type MultiFactorAuthenticationProvider, type MultiFactorCacheStore, type SecurityKey, type SecurityKeyStore } from './multi-factor.ts';
|
|
11
|
+
export { base32Decode, base32Encode, generateTotpSecret, hotp, totpCode, totpTimestep, totpUri, verifyTotp, type TotpAlgorithm, type TotpOptions } from './totp.ts';
|
|
12
|
+
export { qrCodeDataUri, qrCodeMatrix, qrCodeSvg, type QrCodeSvgOptions, type QrErrorCorrection } from './qr-code.ts';
|
|
13
|
+
export { authenticationOptions, decodeCbor, generateChallenge, registrationOptions, verifyAuthentication, verifyRegistration, WebAuthnError, type AuthenticationResponseJSON, type RegistrationResponseJSON, type StoredCredential, type UserVerification } from './webauthn.ts';
|
|
8
14
|
export { MemorySessionStore, type SessionStore } from './session-store.ts';
|
|
9
|
-
export { csvCell, csvLine, DownloadStore, ExportAction, ExportBulkAction, ExportColumn, Exporter, ImportAction, ImportColumn, Importer, parseCsv, type ExportActionInstance, type ImportActionInstance } from './import-export.ts';
|
|
10
|
-
|
|
15
|
+
export { csvCell, csvLine, DownloadStore, ExportAction, ExportBulkAction, ExportColumn, Exporter, ImportAction, ImportColumn, Importer, importExportSchema, knexImportExportHistory, parseCsv, readSpreadsheet, runInBackground, useBackgroundRunner, type BackgroundTask, type ExportActionInstance, type ImportActionInstance, type ImportExportHistory } from './import-export.ts';
|
|
16
|
+
export { buildXlsx, readXlsx } from './xlsx.ts';
|
|
17
|
+
import { DownloadStore, type ImportExportHistory } from './import-export.ts';
|
|
11
18
|
import { type SessionStore } from './session-store.ts';
|
|
12
19
|
import { type CalendarEvent, type CalendarLabels, type CalendarView } from './calendar.ts';
|
|
13
20
|
export { addDays, addMonths, CALENDAR_LABELS, dayOf, daysBetween, eventsOn, renderCalendar, renderCalendarEvent, timeOf, visibleRange, type CalendarEvent, type CalendarLabels, type CalendarModel, type CalendarView } from './calendar.ts';
|
|
@@ -77,6 +84,27 @@ export interface AuthProvider {
|
|
|
77
84
|
getPasswordFingerprint?(user: User): MaybePromise<string>;
|
|
78
85
|
/** Can this user use this panel? Checked on every request and at login (`false`: 403, or "credentials do not match" at login). */
|
|
79
86
|
canAccessPanel?(user: User, panel: Panel): MaybePromise<boolean>;
|
|
87
|
+
/** Stored authenticator app secret (encrypted by Navette), or `null`. */
|
|
88
|
+
getAppAuthenticationSecret?(user: User): MaybePromise<string | null>;
|
|
89
|
+
/** Saves the secret; `null` turns app authentication off. */
|
|
90
|
+
saveAppAuthenticationSecret?(user: User, secret: string | null): MaybePromise<void>;
|
|
91
|
+
/** Stored recovery codes (hashed by Navette), or `null`. */
|
|
92
|
+
getAppAuthenticationRecoveryCodes?(user: User): MaybePromise<string[] | null>;
|
|
93
|
+
saveAppAuthenticationRecoveryCodes?(user: User, codes: string[] | null): MaybePromise<void>;
|
|
94
|
+
/** Whether the user receives sign-in codes by email (`EmailAuthentication`). */
|
|
95
|
+
hasEmailAuthentication?(user: User): MaybePromise<boolean>;
|
|
96
|
+
saveEmailAuthentication?(user: User, enabled: boolean): MaybePromise<void>;
|
|
97
|
+
/** The user's security keys. */
|
|
98
|
+
getSecurityKeys?(user: User): MaybePromise<SecurityKey[]>;
|
|
99
|
+
createSecurityKey?(user: User, key: Omit<SecurityKey, 'id' | 'createdAt' | 'lastUsedAt'>): MaybePromise<void>;
|
|
100
|
+
/** Updates a key of this user (counter and last use after a sign-in). */
|
|
101
|
+
updateSecurityKey?(user: User, id: SecurityKey['id'], changes: {
|
|
102
|
+
signCount?: number;
|
|
103
|
+
lastUsedAt?: string;
|
|
104
|
+
name?: string;
|
|
105
|
+
}): MaybePromise<void>;
|
|
106
|
+
/** Deletes a key of this user. */
|
|
107
|
+
deleteSecurityKey?(user: User, id: SecurityKey['id']): MaybePromise<void>;
|
|
80
108
|
}
|
|
81
109
|
/**
|
|
82
110
|
* Panel tenants (teams, organizations…). Every panel page then lives under
|
|
@@ -96,6 +124,15 @@ export interface TenantProvider<T = any> {
|
|
|
96
124
|
/** Creates a tenant (tenant registration page); the user becomes a member. */
|
|
97
125
|
create?(data: Record<string, unknown>, user: User): MaybePromise<T>;
|
|
98
126
|
update?(tenant: T, data: Record<string, unknown>): MaybePromise<T>;
|
|
127
|
+
/** Tenant the user lands on after signing in (instead of their first one), or `null`. */
|
|
128
|
+
getDefaultTenant?(user: User): MaybePromise<T | null>;
|
|
129
|
+
}
|
|
130
|
+
/** Subscriptions of tenants (`panel.tenantBillingProvider()`): Stripe, Paddle, your own… */
|
|
131
|
+
export interface TenantBillingProvider<T = any> {
|
|
132
|
+
/** The billing page of a tenant (`/<tenant>/billing`): usually a redirect to the payment provider's customer portal. */
|
|
133
|
+
handle(req: Request, res: Response, tenant: T): void | Promise<void>;
|
|
134
|
+
/** Whether the tenant has an active subscription; required by `panel.requiresTenantSubscription()`. */
|
|
135
|
+
isSubscribed?(tenant: T): MaybePromise<boolean>;
|
|
99
136
|
}
|
|
100
137
|
export type PanelContext = {
|
|
101
138
|
req: Request;
|
|
@@ -143,8 +180,14 @@ export declare class NavigationItem {
|
|
|
143
180
|
_isActive: ((path: string) => boolean) | null;
|
|
144
181
|
_badge: string | number | null;
|
|
145
182
|
_badgeColor: string;
|
|
183
|
+
_parentItem: string | null;
|
|
184
|
+
_childItems: NavigationItem[];
|
|
146
185
|
constructor(label: string);
|
|
147
186
|
static make(label: string): NavigationItem;
|
|
187
|
+
/** Shows the item below another item of its group, by that item's label. */
|
|
188
|
+
parentItem(label: string | null): this;
|
|
189
|
+
/** Items shown below this one, when it (or one of them) is active, or when it has no URL. */
|
|
190
|
+
childItems(items: NavigationItem[]): this;
|
|
148
191
|
url(url: string): this;
|
|
149
192
|
icon(icon: string | null): this;
|
|
150
193
|
group(group: string | null): this;
|
|
@@ -176,6 +219,10 @@ export declare class Panel {
|
|
|
176
219
|
private _downloads;
|
|
177
220
|
/** Where files offered for download (exports, failed import rows) are kept: in memory by default. */
|
|
178
221
|
downloads(store: DownloadStore): this;
|
|
222
|
+
private _importExportHistory;
|
|
223
|
+
/** Records each import and export run (`knexImportExportHistory(db)`): who, which file, how many rows, failed import rows. */
|
|
224
|
+
importExportHistory(store: ImportExportHistory | null): this;
|
|
225
|
+
getImportExportHistory(): ImportExportHistory | null;
|
|
179
226
|
getDownloads(): DownloadStore;
|
|
180
227
|
getDownloadUrl(token: string): string;
|
|
181
228
|
private _pages;
|
|
@@ -239,6 +286,12 @@ export declare class Panel {
|
|
|
239
286
|
pages(pages: (typeof Page)[]): this;
|
|
240
287
|
/** Registers resources: their pages and navigation items. */
|
|
241
288
|
resources(resources: (typeof Resource)[]): this;
|
|
289
|
+
/** A page or resource in a cluster is served under the cluster's slug; the cluster page is registered with it. */
|
|
290
|
+
private registerCluster;
|
|
291
|
+
/** Pages (resource list pages included) shown in a cluster's sub-navigation. */
|
|
292
|
+
getClusteredComponents(cluster: typeof Cluster): (typeof Page)[];
|
|
293
|
+
/** Sub-navigation of a cluster, for one of its pages: the components the user can open, sorted, with their groups. */
|
|
294
|
+
getClusterSubNavigation(cluster: typeof Cluster, current: Page, user: User | null): Promise<SubNavigationItem[]>;
|
|
242
295
|
navigationItems(items: NavigationItem[]): this;
|
|
243
296
|
/** Order of navigation groups. */
|
|
244
297
|
/** Order of the navigation groups: labels, or `NavigationGroup.make('Shop').icon('o-shopping-cart').collapsible(false)`. */
|
|
@@ -268,7 +321,14 @@ export declare class Panel {
|
|
|
268
321
|
private _authorizer;
|
|
269
322
|
/** Plugs in your own permission system: consulted before any policy, for every ability, page and widget. */
|
|
270
323
|
authorizeUsing(authorizer: Authorizer | null): this;
|
|
271
|
-
|
|
324
|
+
private _guestAccess;
|
|
325
|
+
/**
|
|
326
|
+
* Lets visitors who are not signed in use the panel's pages. Policies, `canAccess()` and the authorization hook then receive
|
|
327
|
+
* a `null` user: return `true` there for what guests may see. Not available with tenancy.
|
|
328
|
+
*/
|
|
329
|
+
guestAccess(condition?: boolean): this;
|
|
330
|
+
allowsGuests(): boolean;
|
|
331
|
+
/** Authorization decision: the panel hook first, then the given rule. Without a user: `false`, unless the panel allows guests. */
|
|
272
332
|
authorize(user: User | null, ability: string, subject?: {
|
|
273
333
|
resource?: unknown;
|
|
274
334
|
record?: unknown;
|
|
@@ -376,6 +436,27 @@ export declare class Panel {
|
|
|
376
436
|
page: typeof EditProfile;
|
|
377
437
|
isSimple: boolean;
|
|
378
438
|
};
|
|
439
|
+
/** Whether the profile page uses the simple layout (`profile(page, false)`: the full layout, with the navigation). */
|
|
440
|
+
isProfileSimple(): boolean;
|
|
441
|
+
private _multiFactor;
|
|
442
|
+
/**
|
|
443
|
+
* Multi-factor authentication: after the password, users with a provider set up must pass its challenge. They set providers
|
|
444
|
+
* up on their profile page (`profile()`). `isRequired`: users without any provider are sent to a setup page first.
|
|
445
|
+
*/
|
|
446
|
+
multiFactorAuthentication(providers: MultiFactorAuthenticationProvider[], options?: {
|
|
447
|
+
isRequired?: boolean;
|
|
448
|
+
setUpPage?: typeof SetUpRequiredMultiFactorAuthentication;
|
|
449
|
+
}): this;
|
|
450
|
+
hasMultiFactorAuthentication(): boolean;
|
|
451
|
+
getMultiFactorAuthenticationProviders(): MultiFactorAuthenticationProvider[];
|
|
452
|
+
isMultiFactorAuthenticationRequired(): boolean;
|
|
453
|
+
getSetUpRequiredMultiFactorAuthenticationPage(): typeof SetUpRequiredMultiFactorAuthentication | null;
|
|
454
|
+
getSetUpRequiredMultiFactorAuthenticationUrl(): string;
|
|
455
|
+
/** The login challenge of this panel (enabled providers, rate limiting). */
|
|
456
|
+
getMultiFactorChallenge(): MultiFactorChallenge;
|
|
457
|
+
/** Whether the user must set up multi-factor authentication before using the panel. */
|
|
458
|
+
mustSetUpMultiFactorAuthentication(user: User | null): Promise<boolean>;
|
|
459
|
+
usesSecurityKeys(): boolean;
|
|
379
460
|
/** Mailer for password reset and verification emails. Defaults to the console. */
|
|
380
461
|
mailer(mailer: Mailer): this;
|
|
381
462
|
sendMail(message: MailMessage): Promise<void>;
|
|
@@ -391,11 +472,29 @@ export declare class Panel {
|
|
|
391
472
|
passwordResetRequestRouteSlug(slug: string): this;
|
|
392
473
|
passwordResetRouteSlug(slug: string): this;
|
|
393
474
|
/** Paths of the authentication pages, relative to the panel. */
|
|
475
|
+
/** URL segment of the logout route (`logout` by default). */
|
|
476
|
+
logoutRouteSlug(slug: string): this;
|
|
477
|
+
/** URL segments of email verification: `<prefix>/<prompt>` and `<prefix>/<verify>/…` (`email-verification/prompt`, `email-verification/verify`). */
|
|
478
|
+
emailVerificationRoutePrefix(prefix: string): this;
|
|
479
|
+
emailVerificationPromptRouteSlug(slug: string): this;
|
|
480
|
+
emailVerificationRouteSlug(slug: string): this;
|
|
481
|
+
/** URL segments of the email change links: `<prefix>/<verify>` and `<prefix>/block` (`email-change-verification/verify`). */
|
|
482
|
+
emailChangeVerificationRoutePrefix(prefix: string): this;
|
|
483
|
+
emailChangeVerificationRouteSlug(slug: string): this;
|
|
484
|
+
/** URL segments of the required multi-factor setup page (`multi-factor-authentication/set-up`). */
|
|
485
|
+
multiFactorAuthenticationRoutePrefix(prefix: string): this;
|
|
486
|
+
setUpRequiredMultiFactorAuthenticationRouteSlug(slug: string): this;
|
|
394
487
|
getAuthPaths(): {
|
|
395
488
|
login: string;
|
|
396
489
|
registration: string;
|
|
397
490
|
passwordResetRequest: string;
|
|
398
491
|
passwordReset: string;
|
|
492
|
+
logout: string;
|
|
493
|
+
emailVerificationPrompt: string;
|
|
494
|
+
emailVerification: string;
|
|
495
|
+
emailChangeVerification: string;
|
|
496
|
+
blockEmailChange: string;
|
|
497
|
+
setUpRequiredMultiFactor: string;
|
|
399
498
|
};
|
|
400
499
|
/** First URL segments used by the panel itself (never page slugs or tenant keys). */
|
|
401
500
|
getReservedSegments(): Set<string>;
|
|
@@ -410,6 +509,24 @@ export declare class Panel {
|
|
|
410
509
|
getVerifyEmailUrl(user: User): string;
|
|
411
510
|
checkVerifyEmailUrl(id: string, hash: string, expires: unknown, signature: unknown): boolean;
|
|
412
511
|
getEmailVerificationPromptUrl(): string;
|
|
512
|
+
private _emailChangeVerification;
|
|
513
|
+
/**
|
|
514
|
+
* A new email address set on the profile page is only saved once confirmed: a link is sent to the new address, and a link
|
|
515
|
+
* to block the change to the old one. Both are valid for 60 minutes.
|
|
516
|
+
*/
|
|
517
|
+
emailChangeVerification(condition?: boolean): this;
|
|
518
|
+
hasEmailChangeVerification(): boolean;
|
|
519
|
+
/** Links of an email change request: to confirm the new address, and to block the change. */
|
|
520
|
+
getEmailChangeUrls(user: User, newEmail: string): {
|
|
521
|
+
verify: string;
|
|
522
|
+
block: string;
|
|
523
|
+
};
|
|
524
|
+
readEmailChangeToken(kind: 'verify' | 'block', token: unknown): {
|
|
525
|
+
uid: string | number;
|
|
526
|
+
email: string;
|
|
527
|
+
from?: string;
|
|
528
|
+
nonce: string;
|
|
529
|
+
} | null;
|
|
413
530
|
getProfileUrl(): string | null;
|
|
414
531
|
/** Whether the user must still verify their email before using the panel. */
|
|
415
532
|
mustVerifyEmail(user: User | null): Promise<boolean>;
|
|
@@ -438,6 +555,41 @@ export declare class Panel {
|
|
|
438
555
|
getTenantProfilePage(): typeof EditTenantProfile | null;
|
|
439
556
|
tenantMenu(condition?: boolean): this;
|
|
440
557
|
hasTenantMenu(): boolean;
|
|
558
|
+
private _searchableTenantMenu;
|
|
559
|
+
/** A search field above the tenants of the menu. By default (`null`), it is shown from ten tenants to switch to. */
|
|
560
|
+
searchableTenantMenu(condition?: boolean | null): this;
|
|
561
|
+
isTenantMenuSearchable(): boolean | null;
|
|
562
|
+
private _tenantRoutePrefix;
|
|
563
|
+
/** A segment before the tenant key in URLs: `tenantRoutePrefix('team')` serves `/app/team/1/projects`. */
|
|
564
|
+
tenantRoutePrefix(prefix: string | null): this;
|
|
565
|
+
getTenantRoutePrefix(): string | null;
|
|
566
|
+
private _tenantDomain;
|
|
567
|
+
/**
|
|
568
|
+
* Identifies the tenant by the host instead of the path: `tenantDomain('{tenant}.example.com')` serves the tenant `acme` at
|
|
569
|
+
* `acme.example.com/app/projects`. A port may follow (`{tenant}.localhost:3000`).
|
|
570
|
+
*/
|
|
571
|
+
tenantDomain(pattern: string | null): this;
|
|
572
|
+
getTenantDomain(): string | null;
|
|
573
|
+
/** Domain of a tenant, with its port if the pattern has one. */
|
|
574
|
+
getTenantDomainFor(tenant: unknown): string;
|
|
575
|
+
/** Key of the tenant designated by a request (its path, or its host with `tenantDomain()`), unchecked; `null` if none. */
|
|
576
|
+
getTenantKeyFromRequest(path: string, hostname: string): string | null;
|
|
577
|
+
private _tenantBilling;
|
|
578
|
+
private _tenantBillingRouteSlug;
|
|
579
|
+
private _requiresTenantSubscription;
|
|
580
|
+
/** Adds a "Manage subscription" link to the tenant menu, served by the provider at `/<tenant>/billing`. */
|
|
581
|
+
tenantBillingProvider(provider: TenantBillingProvider | null): this;
|
|
582
|
+
getTenantBillingProvider(): TenantBillingProvider | null;
|
|
583
|
+
hasTenantBilling(): boolean;
|
|
584
|
+
tenantBillingRouteSlug(slug: string): this;
|
|
585
|
+
getTenantBillingRouteSlug(): string;
|
|
586
|
+
getTenantBillingUrl(tenant?: unknown): string | null;
|
|
587
|
+
/** The pages of a tenant without an active subscription (`isSubscribed()`) redirect to its billing page. */
|
|
588
|
+
requiresTenantSubscription(condition?: boolean): this;
|
|
589
|
+
isTenantSubscriptionRequired(): boolean;
|
|
590
|
+
private _sessionCookieDomain;
|
|
591
|
+
/** Domain of the session cookie (`.example.com`): with `tenantDomain()`, a sign-in then holds on every tenant's host. */
|
|
592
|
+
sessionCookieDomain(domain: string | null): this;
|
|
441
593
|
tenantMenuItems(items: Record<string, Action> | Action[]): this;
|
|
442
594
|
getTenantMenuItems(): Record<string, Action>;
|
|
443
595
|
/** Runs `fn` for a tenant: generated URLs (`getUrl`) and resource queries are scoped to it. */
|
|
@@ -470,9 +622,41 @@ export declare class Panel {
|
|
|
470
622
|
/** Tenant avatar: from the tenant provider (`getAvatarUrl`), otherwise its initials. */
|
|
471
623
|
getTenantAvatarUrl(tenant: unknown): string;
|
|
472
624
|
private _spa;
|
|
473
|
-
|
|
474
|
-
|
|
625
|
+
private _spaPrefetching;
|
|
626
|
+
/**
|
|
627
|
+
* SPA mode: navigate between panel pages without full reloads (`wire:navigate`). `{ prefetch: true }` loads a page as soon
|
|
628
|
+
* as its link is hovered (`wire:navigate.hover`).
|
|
629
|
+
*/
|
|
630
|
+
spa(condition?: boolean, options?: {
|
|
631
|
+
prefetch?: boolean;
|
|
632
|
+
}): this;
|
|
475
633
|
hasSpaMode(): boolean;
|
|
634
|
+
hasSpaPrefetching(): boolean;
|
|
635
|
+
private _domains;
|
|
636
|
+
/** Serves the panel on this domain only (`admin.example.com`): requests for other hosts go to the next routes. */
|
|
637
|
+
domain(host: string | null): this;
|
|
638
|
+
/** Serves the panel on these domains only. */
|
|
639
|
+
domains(hosts: string[]): this;
|
|
640
|
+
getDomains(): string[];
|
|
641
|
+
private _unsavedChangesAlerts;
|
|
642
|
+
/** Warns the user before leaving a create or edit page whose form has unsaved changes. */
|
|
643
|
+
unsavedChangesAlerts(condition?: boolean): this;
|
|
644
|
+
hasUnsavedChangesAlerts(): boolean;
|
|
645
|
+
private _databaseTransactions;
|
|
646
|
+
/**
|
|
647
|
+
* Wraps the creation and the saving of records (from `beforeCreate()` / `beforeSave()` to `afterCreate()` / `afterSave()`)
|
|
648
|
+
* in a database transaction, rolled back when anything throws. Pages can opt in or out with `static hasDatabaseTransactions`.
|
|
649
|
+
*/
|
|
650
|
+
databaseTransactions(condition?: boolean): this;
|
|
651
|
+
hasDatabaseTransactions(): boolean;
|
|
652
|
+
private _strictAuthorization;
|
|
653
|
+
/** Throws when a resource is checked for an ability its policy does not define, instead of allowing it. */
|
|
654
|
+
strictAuthorization(condition?: boolean): this;
|
|
655
|
+
isAuthorizationStrict(): boolean;
|
|
656
|
+
private _bootCallbacks;
|
|
657
|
+
/** Runs on every request handled by the panel (its pages and their updates), before them. */
|
|
658
|
+
bootUsing(callback: (panel: Panel, req: Request) => void | Promise<void>): this;
|
|
659
|
+
boot(req: Request): Promise<void>;
|
|
476
660
|
getAvatarUrlFor(name: string): string;
|
|
477
661
|
/** Navigation groups: registered pages plus custom items, sorted, grouped in the declared order. */
|
|
478
662
|
/** Computed navigation badges: page URL → value. */
|
|
@@ -483,14 +667,9 @@ export declare class Panel {
|
|
|
483
667
|
label: string | null;
|
|
484
668
|
itemsId: string;
|
|
485
669
|
isActive: boolean;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
icon: string | null;
|
|
490
|
-
isActive: boolean;
|
|
491
|
-
badge: string | number | null;
|
|
492
|
-
badgeColorClasses: string[];
|
|
493
|
-
}[];
|
|
670
|
+
icon: string | null;
|
|
671
|
+
collapsible: boolean;
|
|
672
|
+
items: NavigationItemView[];
|
|
494
673
|
}[];
|
|
495
674
|
private sign;
|
|
496
675
|
logIn(res: Response, user: User, remember: boolean): void;
|
|
@@ -602,6 +781,63 @@ declare abstract class BasePage extends Component implements SchemaHost {
|
|
|
602
781
|
contentSchema(schema: Schema): Schema;
|
|
603
782
|
/** Render hook scopes of this page: its class (and its resource, for a resource page). */
|
|
604
783
|
getRenderHookScopes(): unknown[];
|
|
784
|
+
/** Actions of the page header (full layout). */
|
|
785
|
+
getHeaderActions(): Action[];
|
|
786
|
+
static callable: string[];
|
|
787
|
+
loadedDeferredSchemas: Record<string, boolean>;
|
|
788
|
+
/** Whether a lazy schema is rendered: loaded already, or holding a field with a validation error (loaded so it shows). */
|
|
789
|
+
isDeferredSchemaLoaded(schema: Schema): Promise<boolean>;
|
|
790
|
+
/** Called by the loading indicator of a lazy schema: renders only that schema. */
|
|
791
|
+
loadDeferredSchema(schemaKey: unknown): Promise<void>;
|
|
792
|
+
mountedActions: {
|
|
793
|
+
name: string;
|
|
794
|
+
arguments: Record<string, unknown>;
|
|
795
|
+
context: Record<string, unknown>;
|
|
796
|
+
data?: Record<string, unknown>;
|
|
797
|
+
}[];
|
|
798
|
+
/** Files uploaded by rich editors (`componentFileAttachments.<state path>.<id>`), stored by the editor when it asks for their URL. */
|
|
799
|
+
componentFileAttachments: Record<string, unknown>;
|
|
800
|
+
getActionMountContext(): Record<string, unknown>;
|
|
801
|
+
protected getActionRecord(): unknown;
|
|
802
|
+
/** Record targeted by an action mounted in a given context (table row…). */
|
|
803
|
+
protected resolveActionRecord(_context: Record<string, unknown>): Promise<unknown>;
|
|
804
|
+
/** Actions that can be mounted in a given context (page header by default). */
|
|
805
|
+
protected getMountableActions(context: Record<string, unknown>): Action[] | Promise<Action[]>;
|
|
806
|
+
/**
|
|
807
|
+
* Actions owned by a schema component (Repeater: add, delete, move; Section: header actions).
|
|
808
|
+
* The key comes from the browser: it must match a real component of the schema, or the schema itself.
|
|
809
|
+
*/
|
|
810
|
+
/** Visible component of a schema of this page (or of a mounted action's form) with this key. */
|
|
811
|
+
protected findSchemaComponent(key: string): Promise<any | null>;
|
|
812
|
+
protected getSchemaComponentActions(key: string): Promise<Action[]>;
|
|
813
|
+
/** Context kept for a mounted action: the server's, never the browser's as-is. */
|
|
814
|
+
protected resolveMountContext(clientContext: unknown): Record<string, unknown>;
|
|
815
|
+
private findMountableAction;
|
|
816
|
+
private actionContext;
|
|
817
|
+
/** Form schema of a mounted action: key `mountedActionSchema<n>`, state under `mountedActions.<n>.data`. */
|
|
818
|
+
getMountedActionSchema(index: number): Promise<Schema | null>;
|
|
819
|
+
private renderActionModal;
|
|
820
|
+
/** Cancels a partial modal render prepared by a live field when the same request then runs an action. */
|
|
821
|
+
private cancelModalPartialRender;
|
|
822
|
+
/** Live field in a mounted action's form: runs `afterStateUpdated`, then only returns the affected modal. */
|
|
823
|
+
updatedMountedActions(_value: unknown, key: string | null): Promise<void>;
|
|
824
|
+
/** Calls a method exposed by a field, from that field's JavaScript (e.g. Select search). Does not re-render. */
|
|
825
|
+
callSchemaComponentMethod(componentKey: unknown, method: unknown, args?: unknown): Promise<unknown>;
|
|
826
|
+
/** A hidden action can be neither mounted nor run, even through a call forged by the browser. */
|
|
827
|
+
private assertActionVisible;
|
|
828
|
+
private syncActionModals;
|
|
829
|
+
/** False for a table component: its modals are rendered inside the table. */
|
|
830
|
+
protected rendersActionModalsInPage(): boolean;
|
|
831
|
+
renderActionModals(): Promise<string>;
|
|
832
|
+
/** Mounts an action: without a modal it runs immediately; with one, only the modals fragment is returned. */
|
|
833
|
+
mountAction(name: unknown, args?: unknown, _context?: unknown): Promise<void>;
|
|
834
|
+
/**
|
|
835
|
+
* Replaces the action being run with another action of the same component, opened in its place (to show a result, as
|
|
836
|
+
* the new recovery codes). Call it from the running action; `name` must be mountable in the same context (it may be hidden).
|
|
837
|
+
*/
|
|
838
|
+
replaceMountedAction(name: string, args?: Record<string, unknown>): Promise<void>;
|
|
839
|
+
unmountAction(_shouldCancelParentActions?: unknown): void;
|
|
840
|
+
callMountedAction(_args?: unknown): Promise<void>;
|
|
605
841
|
}
|
|
606
842
|
/** A panel page, in the full layout (sidebar, header). */
|
|
607
843
|
export declare class Page extends BasePage {
|
|
@@ -615,9 +851,15 @@ export declare class Page extends BasePage {
|
|
|
615
851
|
static navigationSort: number;
|
|
616
852
|
static navigationBadge: string | number | null;
|
|
617
853
|
static navigationBadgeColor: string;
|
|
854
|
+
/** Label of another menu item of the group, below which this page is shown. */
|
|
855
|
+
static navigationParentItem: string | null;
|
|
856
|
+
/** Cluster of the page: it is served under the cluster's URL and listed in its sub-navigation. */
|
|
857
|
+
static cluster: typeof Cluster | null;
|
|
618
858
|
static shouldRegisterNavigation: boolean;
|
|
619
859
|
/** Restricts the page to some users: returning `false` hides it from the menu and denies it (403). */
|
|
620
860
|
static canAccess(_user: User): boolean | Promise<boolean>;
|
|
861
|
+
/** Whether the page needs the tenant's subscription (`panel.requiresTenantSubscription()`); `false` keeps it open. */
|
|
862
|
+
static isTenantSubscriptionRequired(panel: Panel): boolean;
|
|
621
863
|
static getTitle(): string;
|
|
622
864
|
static getNavigationLabel(): string;
|
|
623
865
|
/** Every request, including browser updates, requires an authenticated user: a snapshot kept after logout is not enough. */
|
|
@@ -631,8 +873,13 @@ export declare class Page extends BasePage {
|
|
|
631
873
|
}[];
|
|
632
874
|
getHeaderActions(): Action[];
|
|
633
875
|
getPageClasses(): string[];
|
|
634
|
-
/** Page sub-navigation; empty
|
|
876
|
+
/** Page sub-navigation: its cluster's pages, for a page in a cluster; empty otherwise. */
|
|
635
877
|
getSubNavigation(): SubNavigationItem[] | Promise<SubNavigationItem[]>;
|
|
878
|
+
/** Breadcrumbs, preceded by the cluster's for a page in a cluster. */
|
|
879
|
+
protected getBreadcrumbsWithCluster(): {
|
|
880
|
+
label: string;
|
|
881
|
+
url: string | null;
|
|
882
|
+
}[];
|
|
636
883
|
/** Sub-navigation position: `start` (column before the content), `end` (after) or `top` (tabs). Defaults to the resource's, then the panel's. */
|
|
637
884
|
static subNavigationPosition: 'start' | 'end' | 'top' | null;
|
|
638
885
|
getSubNavigationPosition(): 'start' | 'end' | 'top';
|
|
@@ -652,53 +899,33 @@ export declare class Page extends BasePage {
|
|
|
652
899
|
/** Widget grid of a schema: each widget is a child component, lazy-loaded by default. */
|
|
653
900
|
protected renderWidgetsGrid(widgets: (typeof Widget)[], columns: number | Record<string, number>, schemaKey: string): Promise<string>;
|
|
654
901
|
/** Page content: its `content` schema by default. */
|
|
655
|
-
protected renderContent(): Promise<string>;
|
|
656
|
-
mountedActions: {
|
|
657
|
-
name: string;
|
|
658
|
-
arguments: Record<string, unknown>;
|
|
659
|
-
context: Record<string, unknown>;
|
|
660
|
-
data?: Record<string, unknown>;
|
|
661
|
-
}[];
|
|
662
|
-
/** Files uploaded by rich editors (`componentFileAttachments.<state path>.<id>`), stored by the editor when it asks for their URL. */
|
|
663
|
-
componentFileAttachments: Record<string, unknown>;
|
|
664
|
-
getActionMountContext(): Record<string, unknown>;
|
|
665
|
-
protected getActionRecord(): unknown;
|
|
666
|
-
/** Record targeted by an action mounted in a given context (table row…). */
|
|
667
|
-
protected resolveActionRecord(_context: Record<string, unknown>): Promise<unknown>;
|
|
668
|
-
/** Actions that can be mounted in a given context (page header by default). */
|
|
669
|
-
protected getMountableActions(context: Record<string, unknown>): Action[] | Promise<Action[]>;
|
|
670
902
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
903
|
+
* Content rendered from an EJS view instead of the `content` schema: a namespaced view (`'app::pages.report'`), or a file
|
|
904
|
+
* (path or `new URL('./report.ejs', import.meta.url)`). The view receives `page` (this page) and `getViewData()`, and may use
|
|
905
|
+
* component tags (`<x-section>`, `<x-button>`…). Elements calling the server use `wire:click` on a method listed in `static callable`.
|
|
673
906
|
*/
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
protected resolveMountContext(clientContext: unknown): Record<string, unknown>;
|
|
679
|
-
private findMountableAction;
|
|
680
|
-
private actionContext;
|
|
681
|
-
/** Form schema of a mounted action: key `mountedActionSchema<n>`, state under `mountedActions.<n>.data`. */
|
|
682
|
-
getMountedActionSchema(index: number): Promise<Schema | null>;
|
|
683
|
-
private renderActionModal;
|
|
684
|
-
/** Cancels a partial modal render prepared by a live field when the same request then runs an action. */
|
|
685
|
-
private cancelModalPartialRender;
|
|
686
|
-
/** Live field in a mounted action's form: runs `afterStateUpdated`, then only returns the affected modal. */
|
|
687
|
-
updatedMountedActions(_value: unknown, key: string | null): Promise<void>;
|
|
688
|
-
/** Calls a method exposed by a field, from that field's JavaScript (e.g. Select search). Does not re-render. */
|
|
689
|
-
callSchemaComponentMethod(componentKey: unknown, method: unknown, args?: unknown): Promise<unknown>;
|
|
690
|
-
/** A hidden action can be neither mounted nor run, even through a call forged by the browser. */
|
|
691
|
-
private assertActionVisible;
|
|
692
|
-
private syncActionModals;
|
|
693
|
-
/** False for a table component: its modals are rendered inside the table. */
|
|
694
|
-
protected rendersActionModalsInPage(): boolean;
|
|
695
|
-
renderActionModals(): Promise<string>;
|
|
696
|
-
/** Mounts an action: without a modal it runs immediately; with one, only the modals fragment is returned. */
|
|
697
|
-
mountAction(name: unknown, args?: unknown, _context?: unknown): Promise<void>;
|
|
698
|
-
unmountAction(_shouldCancelParentActions?: unknown): void;
|
|
699
|
-
callMountedAction(_args?: unknown): Promise<void>;
|
|
907
|
+
static view: string | URL | null;
|
|
908
|
+
/** Extra variables for the page view. */
|
|
909
|
+
getViewData(): Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
910
|
+
protected renderContent(): Promise<string>;
|
|
700
911
|
render(): Promise<string>;
|
|
701
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Cluster: a menu item gathering pages and resources (`static cluster = Settings`), listed in its sub-navigation and served
|
|
915
|
+
* under its URL (`/settings/...`). Opening the cluster goes to its first page.
|
|
916
|
+
*/
|
|
917
|
+
export declare class Cluster extends Page {
|
|
918
|
+
/** Breadcrumb before those of its pages; defaults to the title. */
|
|
919
|
+
static clusterBreadcrumb: string | null;
|
|
920
|
+
/** `false`: the pages of the cluster have no sub-navigation. */
|
|
921
|
+
static shouldRegisterSubNavigation: boolean;
|
|
922
|
+
static getTitle(): string;
|
|
923
|
+
/** URL segment: `slug`, or the class name without "Cluster" in kebab case (`ProductsCluster` → `products`). */
|
|
924
|
+
static getSlug(): string;
|
|
925
|
+
static getClusterBreadcrumb(): string;
|
|
926
|
+
mount(): Promise<void>;
|
|
927
|
+
getSubNavigation(): SubNavigationItem[] | Promise<SubNavigationItem[]>;
|
|
928
|
+
}
|
|
702
929
|
/**
|
|
703
930
|
* Dashboard: a widget grid, preceded by a filters form when the page defines `filtersFormSchema(schema)`.
|
|
704
931
|
* The filters are passed to widgets as `pageFilters`.
|
|
@@ -721,16 +948,6 @@ export declare class Dashboard extends Page {
|
|
|
721
948
|
getWidgetData(): Record<string, unknown>;
|
|
722
949
|
protected renderContent(): Promise<string>;
|
|
723
950
|
}
|
|
724
|
-
/** In-memory rate limiter: `max` attempts per window, per key. Fine for a single instance; use a shared store when running several. */
|
|
725
|
-
export declare class Throttle {
|
|
726
|
-
private hits;
|
|
727
|
-
private max;
|
|
728
|
-
private windowMs;
|
|
729
|
-
constructor(max?: number, windowMs?: number);
|
|
730
|
-
/** Records an attempt; returns 0 if allowed, otherwise the number of seconds to wait. */
|
|
731
|
-
hit(key: string, now?: number): number;
|
|
732
|
-
clear(key: string): void;
|
|
733
|
-
}
|
|
734
951
|
/** Page with the simple layout (login, registration…). */
|
|
735
952
|
export declare abstract class SimplePage extends BasePage {
|
|
736
953
|
abstract getTitle(): string;
|
|
@@ -805,8 +1022,17 @@ export declare class EditProfile extends SimplePage {
|
|
|
805
1022
|
boot(): Promise<void>;
|
|
806
1023
|
getTitle(): string;
|
|
807
1024
|
hasLogo(): boolean;
|
|
1025
|
+
/** Whether the page uses the full layout (`panel.profile(EditProfile, false)`): its form is then split into sections. */
|
|
1026
|
+
usesFullLayout(): boolean;
|
|
1027
|
+
render(): Promise<string>;
|
|
808
1028
|
formSchema(schema: Schema): Schema;
|
|
1029
|
+
/** Fields of the form: name, email, new password, its confirmation, current password. */
|
|
1030
|
+
protected getFormComponents(): SchemaComponent[];
|
|
809
1031
|
contentSchema(schema: Schema): Schema;
|
|
1032
|
+
/** Emails of an email change: a confirmation link to the new address, a link to block it to the old one. */
|
|
1033
|
+
protected sendEmailChangeVerification(user: User, newEmail: string): Promise<void>;
|
|
1034
|
+
/** Multi-factor authentication settings: each provider's status and actions (set up, turn off…). */
|
|
1035
|
+
protected multiFactorAuthenticationSection(panel: Panel, user: User): SchemaComponent;
|
|
810
1036
|
save(): Promise<void>;
|
|
811
1037
|
}
|
|
812
1038
|
/** Tenant registration page: the subclass provides its label and form (`formSchema`). */
|
|
@@ -843,14 +1069,42 @@ export declare abstract class EditTenantProfile extends Page {
|
|
|
843
1069
|
}
|
|
844
1070
|
export declare class Login extends SimplePage {
|
|
845
1071
|
static callable: string[];
|
|
1072
|
+
/** Signed token naming the user undertaking the multi-factor challenge: set by the server only. */
|
|
1073
|
+
static locked: string[];
|
|
846
1074
|
data: Record<string, unknown>;
|
|
1075
|
+
userUndertakingMultiFactorAuthentication: string | null;
|
|
1076
|
+
/** User of the pending challenge, resolved from the token on each request. */
|
|
1077
|
+
protected _multiFactorUser: User | null;
|
|
1078
|
+
protected _multiFactorProviders: MultiFactorAuthenticationProvider[];
|
|
847
1079
|
mount(): void;
|
|
1080
|
+
boot(): Promise<void>;
|
|
1081
|
+
protected readMultiFactorToken(): Record<string, unknown> | null;
|
|
1082
|
+
/** Whether the multi-factor challenge is shown. */
|
|
1083
|
+
isUndertakingMultiFactorAuthentication(): boolean;
|
|
848
1084
|
getTitle(): string;
|
|
849
1085
|
getHeading(): string;
|
|
850
1086
|
getSubheading(): string | null;
|
|
1087
|
+
getSchema(name: string): Schema;
|
|
851
1088
|
formSchema(schema: Schema): Schema;
|
|
1089
|
+
/** Challenge fields of the user's enabled providers (with a picker when there are several). */
|
|
1090
|
+
multiFactorChallengeFormSchema(schema: Schema): Schema;
|
|
852
1091
|
contentSchema(schema: Schema): Schema;
|
|
1092
|
+
/** Starts a challenge for this user if one of their providers is set up; returns whether it did. */
|
|
1093
|
+
protected startMultiFactorChallenge(user: User): Promise<boolean>;
|
|
853
1094
|
authenticate(): Promise<void>;
|
|
1095
|
+
/** The user picked another provider in the challenge: run its `beforeChallenge` (an email code is sent). */
|
|
1096
|
+
updatedData(value: unknown, key: string | null): Promise<void>;
|
|
1097
|
+
protected cancelMultiFactorChallenge(): void;
|
|
1098
|
+
}
|
|
1099
|
+
/** Shown after sign-in when the panel requires multi-factor authentication and the user has not set any provider up. */
|
|
1100
|
+
export declare class SetUpRequiredMultiFactorAuthentication extends SimplePage {
|
|
1101
|
+
static callable: string[];
|
|
1102
|
+
boot(): Promise<void>;
|
|
1103
|
+
mount(): Promise<void>;
|
|
1104
|
+
getTitle(): string;
|
|
1105
|
+
getHeading(): string;
|
|
1106
|
+
getSubheading(): string | null;
|
|
1107
|
+
contentSchema(schema: Schema): Schema;
|
|
854
1108
|
}
|
|
855
1109
|
/** Whether the host component's user has this ability on the component's resource (or relation manager). Use it in your actions' `authorize()`. */
|
|
856
1110
|
export declare const can: (livewire: unknown, ability: string, record?: unknown) => Promise<boolean> | boolean;
|
|
@@ -864,6 +1118,12 @@ export declare abstract class Resource {
|
|
|
864
1118
|
static navigationGroup: string | null;
|
|
865
1119
|
static navigationSort: number;
|
|
866
1120
|
static navigationBadge: string | number | null;
|
|
1121
|
+
/** Menu label; defaults to the plural model label in title case. */
|
|
1122
|
+
static navigationLabel: string | null;
|
|
1123
|
+
/** Label of another menu item of the group, below which the resource is shown. */
|
|
1124
|
+
static navigationParentItem: string | null;
|
|
1125
|
+
/** Cluster of the resource: its pages are served under the cluster's URL, and its list is in the cluster's sub-navigation. */
|
|
1126
|
+
static cluster: typeof Cluster | null;
|
|
867
1127
|
static adapter: DataAdapter;
|
|
868
1128
|
/** In a multi-tenant panel, whether records belong to each tenant (`true` by default). */
|
|
869
1129
|
static isScopedToTenant: boolean;
|
|
@@ -889,8 +1149,9 @@ export declare abstract class Resource {
|
|
|
889
1149
|
static widgets: (typeof Widget)[];
|
|
890
1150
|
/** Widgets shown above the table on the list page. */
|
|
891
1151
|
static headerWidgets: (typeof Widget)[];
|
|
892
|
-
/** Relation managers shown below the edit form. */
|
|
893
|
-
static relations:
|
|
1152
|
+
/** Relation managers shown below the edit form (and the view page), or groups of them (`RelationGroup`). */
|
|
1153
|
+
static relations: (typeof RelationManager | RelationGroup)[];
|
|
1154
|
+
static getRelations(): (typeof RelationManager | RelationGroup)[];
|
|
894
1155
|
/** Tabs of the list page. */
|
|
895
1156
|
static tabs(): Record<string, Tab>;
|
|
896
1157
|
/** Simple resource: a single page, with create and edit in modals. */
|
|
@@ -899,7 +1160,7 @@ export declare abstract class Resource {
|
|
|
899
1160
|
static getTitleCasePluralModelLabel(): string;
|
|
900
1161
|
static getUrl(page?: string, record?: string | number): string;
|
|
901
1162
|
/** Related records pages: `{ comments: ManagePostComments }` is served at `<slug>/{record}/comments`. */
|
|
902
|
-
static relatedPages: Record<string, typeof ManageRelatedRecords>;
|
|
1163
|
+
static relatedPages: Record<string, typeof ManageRelatedRecords | typeof EditRecord | typeof ViewRecord>;
|
|
903
1164
|
/** Pages listed in a record's sub-navigation, in order: `['view', 'edit', 'comments']`. Empty: no sub-navigation. */
|
|
904
1165
|
static recordSubNavigation: string[];
|
|
905
1166
|
/** Record sub-navigation: one tab per declared page, the current page's tab active. */
|
|
@@ -929,6 +1190,11 @@ export declare abstract class Resource {
|
|
|
929
1190
|
static getGlobalSearchResultTitle(record: any): string;
|
|
930
1191
|
/** Details shown under the title: `{ Author: 'Jane' }`. Empty values are skipped. */
|
|
931
1192
|
static getGlobalSearchResultDetails(_record: any): Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
1193
|
+
/**
|
|
1194
|
+
* Buttons under a result: links (`Action.make('edit').url(…)`) or Livewire events (`.dispatch('quickView', [id])`).
|
|
1195
|
+
* They are not mounted: an action needing a modal belongs on the record's pages.
|
|
1196
|
+
*/
|
|
1197
|
+
static getGlobalSearchResultActions(_record: any): Action[] | Promise<Action[]>;
|
|
932
1198
|
/** URL of a result: the view page, otherwise the edit page; for a simple resource, the list with the action open. `null` if the user has no access. */
|
|
933
1199
|
static getGlobalSearchResultUrl(record: any, user: User | null): Promise<string | null>;
|
|
934
1200
|
static getGlobalSearchResults(search: string, user: User | null): Promise<GlobalSearchResult[]>;
|
|
@@ -940,13 +1206,29 @@ export declare abstract class Resource {
|
|
|
940
1206
|
static getPages(): Record<string, typeof Page>;
|
|
941
1207
|
private static buildPages;
|
|
942
1208
|
}
|
|
943
|
-
/** Sub-navigation tab of a page. */
|
|
1209
|
+
/** Sub-navigation tab of a page; items with a `group` are gathered under its label. */
|
|
944
1210
|
export type SubNavigationItem = {
|
|
945
1211
|
label: string;
|
|
946
1212
|
icon: string | null;
|
|
947
1213
|
url: string;
|
|
948
1214
|
active: boolean;
|
|
1215
|
+
group?: string | null;
|
|
1216
|
+
badge?: string | number | null;
|
|
1217
|
+
badgeColor?: string;
|
|
1218
|
+
};
|
|
1219
|
+
/** A sidebar item, as rendered. */
|
|
1220
|
+
export type NavigationItemView = {
|
|
1221
|
+
label: string;
|
|
1222
|
+
url: string;
|
|
1223
|
+
icon: string | null;
|
|
1224
|
+
isActive: boolean;
|
|
1225
|
+
badge: string | number | null;
|
|
1226
|
+
badgeColorClasses: string[];
|
|
1227
|
+
childItems: NavigationItemView[];
|
|
1228
|
+
activeChildItems: boolean;
|
|
949
1229
|
};
|
|
1230
|
+
/** The cluster a page belongs to: its own, or its resource's. */
|
|
1231
|
+
export declare const clusterOf: (page: unknown) => typeof Cluster | null;
|
|
950
1232
|
type ColumnSpan = number | 'full' | Record<string, number | 'full'>;
|
|
951
1233
|
/** Widget: a component displayed in a page's widget grid. */
|
|
952
1234
|
export declare abstract class Widget extends Component {
|
|
@@ -1303,6 +1585,27 @@ export declare class EmbeddedTable extends SchemaComponent {
|
|
|
1303
1585
|
static make(): EmbeddedTable;
|
|
1304
1586
|
toHtml(): Promise<string>;
|
|
1305
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Several relation managers under one tab of a record page: `RelationGroup.make('Contacts', [PhonesManager, EmailsManager])`.
|
|
1590
|
+
* The managers are shown one below the other.
|
|
1591
|
+
*/
|
|
1592
|
+
export declare class RelationGroup {
|
|
1593
|
+
private _label;
|
|
1594
|
+
private _managers;
|
|
1595
|
+
private _icon;
|
|
1596
|
+
private _badge;
|
|
1597
|
+
private _badgeColor;
|
|
1598
|
+
constructor(label: string, managers: (typeof RelationManager)[]);
|
|
1599
|
+
static make(label: string, managers: (typeof RelationManager)[]): RelationGroup;
|
|
1600
|
+
icon(name: string | null): this;
|
|
1601
|
+
badge(badge: string | number | null | (() => string | number | null | Promise<string | number | null>)): this;
|
|
1602
|
+
badgeColor(color: string): this;
|
|
1603
|
+
getLabel(): string;
|
|
1604
|
+
getIcon(): string | null;
|
|
1605
|
+
getManagers(): (typeof RelationManager)[];
|
|
1606
|
+
getBadge(): Promise<string | number | null>;
|
|
1607
|
+
getBadgeColor(): string;
|
|
1608
|
+
}
|
|
1306
1609
|
/** List page tab: restricts the table query and can show a badge. */
|
|
1307
1610
|
export declare class Tab {
|
|
1308
1611
|
private _label;
|
|
@@ -1335,6 +1638,8 @@ export declare class Tab {
|
|
|
1335
1638
|
export declare class ListRecords extends Page implements TableHost {
|
|
1336
1639
|
static callable: string[];
|
|
1337
1640
|
static resource: typeof Resource;
|
|
1641
|
+
/** In a cluster, the list page shows the cluster's sub-navigation. */
|
|
1642
|
+
getSubNavigation(): SubNavigationItem[] | Promise<SubNavigationItem[]>;
|
|
1338
1643
|
tableSearch: string;
|
|
1339
1644
|
tableSort: string | null;
|
|
1340
1645
|
tableColumnSearches: Record<string, string>;
|
|
@@ -1459,6 +1764,19 @@ export declare class RelationManager extends ListRecords {
|
|
|
1459
1764
|
static table(table: Table): Table;
|
|
1460
1765
|
static form(schema: Schema): Schema;
|
|
1461
1766
|
static getTitle(): string;
|
|
1767
|
+
/** Icon of the manager's tab. */
|
|
1768
|
+
static icon: string | null;
|
|
1769
|
+
static getIcon(): string | null;
|
|
1770
|
+
/** Badge of the manager's tab (a count of related records…), and its color. */
|
|
1771
|
+
static badge: string | number | null;
|
|
1772
|
+
static badgeColor: string;
|
|
1773
|
+
static getBadge(_ownerRecord: any, _pageClass: typeof Page): string | number | null | Promise<string | number | null>;
|
|
1774
|
+
static getBadgeColor(): string;
|
|
1775
|
+
/**
|
|
1776
|
+
* Whether the manager is shown for this record and page (all by default). Hide it here rather than by denying `viewAny`,
|
|
1777
|
+
* which would forbid the whole page.
|
|
1778
|
+
*/
|
|
1779
|
+
static canViewForRecord(_ownerRecord: any, _pageClass: typeof Page): boolean | Promise<boolean>;
|
|
1462
1780
|
/** Owner record key: set on mount by the page, never writable from the browser. */
|
|
1463
1781
|
ownerRecord: string;
|
|
1464
1782
|
static locked: string[];
|
|
@@ -1466,8 +1784,16 @@ export declare class RelationManager extends ListRecords {
|
|
|
1466
1784
|
/** What ListRecords expects from a resource, provided here by the relation manager. */
|
|
1467
1785
|
get resource(): any;
|
|
1468
1786
|
getTable(): Table;
|
|
1469
|
-
/** Attaches an existing record to the owner. */
|
|
1470
|
-
static attach(_ownerKey: string, _recordKey: string): Promise<void>;
|
|
1787
|
+
/** Attaches an existing record to the owner; `pivot` holds the values of the `pivotForm()` fields. */
|
|
1788
|
+
static attach(_ownerKey: string, _recordKey: string, _pivot?: Record<string, unknown>): Promise<void>;
|
|
1789
|
+
/**
|
|
1790
|
+
* Fields stored on the pivot table (a role, a quantity…): added to the attach modal and to the edit form of a row.
|
|
1791
|
+
* Their values go to `attach()` and `updatePivot()`, never to the related record. List the pivot columns in the
|
|
1792
|
+
* relation's query (`adapterFor()`) so that the table and the edit form can read them.
|
|
1793
|
+
*/
|
|
1794
|
+
static pivotForm(_schema: Schema): SchemaComponent[];
|
|
1795
|
+
/** Saves the pivot fields of an attached record (edit form of a row). */
|
|
1796
|
+
static updatePivot(_ownerKey: string, _recordKey: string, _pivot: Record<string, unknown>): Promise<void>;
|
|
1471
1797
|
/** Detaches records from the owner without deleting them. */
|
|
1472
1798
|
static detach(_ownerKey: string, _recordKeys: string[]): Promise<void>;
|
|
1473
1799
|
/** Attachable records matching the search: [key, label]. */
|
|
@@ -1537,6 +1863,20 @@ declare abstract class ResourceFormPage extends Page {
|
|
|
1537
1863
|
updatedData(_value: unknown, key: string | null): Promise<void>;
|
|
1538
1864
|
static resource: typeof Resource;
|
|
1539
1865
|
data: Record<string, unknown>;
|
|
1866
|
+
/** Hash of the form data as last saved: the browser compares it with the current data (unsaved changes alert). */
|
|
1867
|
+
savedDataHash: string;
|
|
1868
|
+
static locked: string[];
|
|
1869
|
+
/** `true` / `false` overrides the panel's `databaseTransactions()` for this page. */
|
|
1870
|
+
static hasDatabaseTransactions: boolean | null;
|
|
1871
|
+
/** `true` / `false` overrides the panel's `unsavedChangesAlerts()` for this page. */
|
|
1872
|
+
static hasUnsavedDataChangesAlert: boolean | null;
|
|
1873
|
+
protected hasDatabaseTransactions(): boolean;
|
|
1874
|
+
protected hasUnsavedDataChangesAlert(): boolean;
|
|
1875
|
+
/** Runs `fn` in a database transaction of the resource's adapter, when the page uses them. */
|
|
1876
|
+
protected wrapInDatabaseTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
1877
|
+
/** Remembers the form data as saved: leaving the page with other data asks for confirmation. */
|
|
1878
|
+
protected rememberData(): void;
|
|
1879
|
+
render(): Promise<string>;
|
|
1540
1880
|
get resource(): typeof Resource;
|
|
1541
1881
|
getDataAdapter(): DataAdapter;
|
|
1542
1882
|
getRecordKey(): string | null;
|
|
@@ -1663,6 +2003,7 @@ export declare class DissociateBulkAction {
|
|
|
1663
2003
|
static make(): Action;
|
|
1664
2004
|
}
|
|
1665
2005
|
/** Restores a soft-deleted record. Only visible when the record is deleted. */
|
|
2006
|
+
/** Restores a soft-deleted record: the page's record, or a table row. Only visible when the record is deleted. */
|
|
1666
2007
|
export declare class RestoreAction {
|
|
1667
2008
|
static make(): Action;
|
|
1668
2009
|
}
|
|
@@ -1697,7 +2038,19 @@ export declare class EditRecord extends ResourceFormPage {
|
|
|
1697
2038
|
*/
|
|
1698
2039
|
previousUrl: string;
|
|
1699
2040
|
protected getCancelUrl(): string;
|
|
2041
|
+
/** Relation managers of this page: the resource's by default (`static relationManagers` on the page replaces them). */
|
|
2042
|
+
getRelationManagers(): (typeof RelationManager | RelationGroup)[];
|
|
2043
|
+
/** `true`: the form becomes the first tab, next to the relation managers. */
|
|
2044
|
+
hasCombinedRelationManagerTabsWithContent(): boolean;
|
|
2045
|
+
getContentTabLabel(): string;
|
|
2046
|
+
getContentTabIcon(): string | null;
|
|
2047
|
+
protected withRelationManagers(content: Schema): Schema;
|
|
1700
2048
|
contentSchema(schema: Schema): Schema;
|
|
2049
|
+
/**
|
|
2050
|
+
* Saves the fields of one component of the form (a section with its own "Save" action), after validating them only:
|
|
2051
|
+
* `livewire.saveFormComponentOnly('contact')` (the component's `key()`, or the component).
|
|
2052
|
+
*/
|
|
2053
|
+
saveFormComponentOnly(component: SchemaComponent | string, notificationTitle?: string | null): Promise<void>;
|
|
1701
2054
|
static locked: string[];
|
|
1702
2055
|
_record: any;
|
|
1703
2056
|
mount(params: Record<string, unknown>): Promise<void>;
|
|
@@ -1752,6 +2105,13 @@ export declare class ViewRecord extends Page {
|
|
|
1752
2105
|
getActionMountContext(): Record<string, unknown>;
|
|
1753
2106
|
protected getActionRecord(): unknown;
|
|
1754
2107
|
infolistSchema(schema: Schema): Schema;
|
|
2108
|
+
/** Relation managers of this page: the resource's by default (`static relationManagers` on the page replaces them). */
|
|
2109
|
+
getRelationManagers(): (typeof RelationManager | RelationGroup)[];
|
|
2110
|
+
/** `true`: the infolist becomes the first tab, next to the relation managers. */
|
|
2111
|
+
hasCombinedRelationManagerTabsWithContent(): boolean;
|
|
2112
|
+
getContentTabLabel(): string;
|
|
2113
|
+
getContentTabIcon(): string | null;
|
|
2114
|
+
protected withRelationManagers(content: Schema): Schema;
|
|
1755
2115
|
contentSchema(schema: Schema): Schema;
|
|
1756
2116
|
/** Defaults to a link to the edit page. */
|
|
1757
2117
|
getHeaderActions(): Action[];
|
|
@@ -1764,6 +2124,17 @@ export declare class ViewRecord extends Page {
|
|
|
1764
2124
|
getSubNavigation(): SubNavigationItem[];
|
|
1765
2125
|
getPageClasses(): string[];
|
|
1766
2126
|
}
|
|
2127
|
+
/** Tenant menu: current tenant, other tenants, registration, profile. */
|
|
2128
|
+
/** Options of an error page: a description replacing the status's own, and the error (its stack is shown in development). */
|
|
2129
|
+
export type ErrorPageOptions = {
|
|
2130
|
+
description?: string;
|
|
2131
|
+
error?: unknown;
|
|
2132
|
+
};
|
|
2133
|
+
/**
|
|
2134
|
+
* Answers with the panel's error page for a status (404, 403, 500…): the cable car in trouble, in the request's theme.
|
|
2135
|
+
* Requests that do not accept HTML get plain text. Use it in your own middleware, a maintenance mode answering 503 for example.
|
|
2136
|
+
*/
|
|
2137
|
+
export declare function renderErrorPage(panel: Panel, req: Request, res: Response, status: number, options?: ErrorPageOptions): void;
|
|
1767
2138
|
/** Sidebar: logo, tenant menu, search, navigation and, without a topbar, the bell and user menu. */
|
|
1768
2139
|
export declare class Sidebar extends Component {
|
|
1769
2140
|
static callable: string[];
|
|
@@ -1799,3 +2170,5 @@ export declare const discoverWidgets: (directory: string | URL) => Promise<(type
|
|
|
1799
2170
|
/** Panels default-exported (`export default Panel.make()…`) by a folder's modules. */
|
|
1800
2171
|
export declare function discoverPanels(directory: string | URL): Promise<Panel[]>;
|
|
1801
2172
|
export declare function panelRouter(panel: Panel, runtime: WireRuntime): Router;
|
|
2173
|
+
/** The panel's 404 page for URLs nothing else answered: mount it after your routes, for a panel served at the root. */
|
|
2174
|
+
export declare function panelNotFound(panel: Panel): (req: Request, res: Response) => void;
|