@navette-js/panels 1.0.0-beta.3 → 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 +2 -1
- 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 +185 -0
- package/lib/import-export.js +543 -0
- package/lib/index.d.ts +629 -79
- package/lib/index.js +1945 -333
- package/lib/multi-factor.d.ts +348 -0
- package/lib/multi-factor.js +928 -0
- package/lib/notifications.d.ts +20 -2
- package/lib/notifications.js +34 -4
- package/lib/qr-code.d.ts +16 -0
- package/lib/qr-code.js +255 -0
- package/lib/session-store.d.ts +21 -0
- package/lib/session-store.js +35 -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 +7 -0
- package/lib/xlsx.js +171 -0
- package/package.json +9 -9
- package/views/components/tenant-menu.ejs +3 -3
- package/views/components/user-menu.ejs +3 -2
- package/views/errors/page.ejs +87 -0
- package/views/layout/base.ejs +9 -1
- package/views/livewire/sidebar.ejs +35 -20
- 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,10 +1,21 @@
|
|
|
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';
|
|
14
|
+
export { MemorySessionStore, type SessionStore } from './session-store.ts';
|
|
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';
|
|
18
|
+
import { type SessionStore } from './session-store.ts';
|
|
8
19
|
import { type CalendarEvent, type CalendarLabels, type CalendarView } from './calendar.ts';
|
|
9
20
|
export { addDays, addMonths, CALENDAR_LABELS, dayOf, daysBetween, eventsOn, renderCalendar, renderCalendarEvent, timeOf, visibleRange, type CalendarEvent, type CalendarLabels, type CalendarModel, type CalendarView } from './calendar.ts';
|
|
10
21
|
import { type DatabaseNotificationStore, type GlobalSearchCategory, type GlobalSearchResult } from './topbar.ts';
|
|
@@ -71,6 +82,29 @@ export interface AuthProvider {
|
|
|
71
82
|
markEmailAsVerified?(user: User): MaybePromise<void>;
|
|
72
83
|
/** A value that changes with the stored password, so a used reset link becomes invalid. */
|
|
73
84
|
getPasswordFingerprint?(user: User): MaybePromise<string>;
|
|
85
|
+
/** Can this user use this panel? Checked on every request and at login (`false`: 403, or "credentials do not match" at login). */
|
|
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>;
|
|
74
108
|
}
|
|
75
109
|
/**
|
|
76
110
|
* Panel tenants (teams, organizations…). Every panel page then lives under
|
|
@@ -90,6 +124,15 @@ export interface TenantProvider<T = any> {
|
|
|
90
124
|
/** Creates a tenant (tenant registration page); the user becomes a member. */
|
|
91
125
|
create?(data: Record<string, unknown>, user: User): MaybePromise<T>;
|
|
92
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>;
|
|
93
136
|
}
|
|
94
137
|
export type PanelContext = {
|
|
95
138
|
req: Request;
|
|
@@ -97,7 +140,8 @@ export type PanelContext = {
|
|
|
97
140
|
panel: Panel;
|
|
98
141
|
user: User | null;
|
|
99
142
|
mailbox: string;
|
|
100
|
-
tenant?: unknown;
|
|
143
|
+
tenant?: unknown; /** Signed in through the shared session, but not allowed into this panel. */
|
|
144
|
+
denied?: boolean;
|
|
101
145
|
};
|
|
102
146
|
/**
|
|
103
147
|
* Predefined panel theme: a set of colors (palettes or plain colors), optionally a font and some CSS.
|
|
@@ -114,6 +158,19 @@ export type Theme = {
|
|
|
114
158
|
/** CSS added to the page for this theme (written by the panel developer, inserted as-is). */
|
|
115
159
|
css?: string;
|
|
116
160
|
};
|
|
161
|
+
/** Navigation group: label, icon (then its items have none, as in Filament) and whether it can be collapsed. */
|
|
162
|
+
export declare class NavigationGroup {
|
|
163
|
+
private _label;
|
|
164
|
+
private _icon;
|
|
165
|
+
private _collapsible;
|
|
166
|
+
constructor(label: string);
|
|
167
|
+
static make(label: string): NavigationGroup;
|
|
168
|
+
icon(name: string | null): this;
|
|
169
|
+
collapsible(condition?: boolean): this;
|
|
170
|
+
getLabel(): string;
|
|
171
|
+
getIcon(): string | null;
|
|
172
|
+
isCollapsible(): boolean;
|
|
173
|
+
}
|
|
117
174
|
export declare class NavigationItem {
|
|
118
175
|
label: string;
|
|
119
176
|
_url: string;
|
|
@@ -123,8 +180,14 @@ export declare class NavigationItem {
|
|
|
123
180
|
_isActive: ((path: string) => boolean) | null;
|
|
124
181
|
_badge: string | number | null;
|
|
125
182
|
_badgeColor: string;
|
|
183
|
+
_parentItem: string | null;
|
|
184
|
+
_childItems: NavigationItem[];
|
|
126
185
|
constructor(label: string);
|
|
127
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;
|
|
128
191
|
url(url: string): this;
|
|
129
192
|
icon(icon: string | null): this;
|
|
130
193
|
group(group: string | null): this;
|
|
@@ -149,10 +212,24 @@ export declare class Panel {
|
|
|
149
212
|
private _login;
|
|
150
213
|
private _auth;
|
|
151
214
|
private _key;
|
|
215
|
+
private _sessionStore;
|
|
216
|
+
/** Where per-browser data is kept (persisted table filters, search and sort): in memory by default. */
|
|
217
|
+
sessionStore(store: SessionStore): this;
|
|
218
|
+
getSessionStore(): SessionStore;
|
|
219
|
+
private _downloads;
|
|
220
|
+
/** Where files offered for download (exports, failed import rows) are kept: in memory by default. */
|
|
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;
|
|
226
|
+
getDownloads(): DownloadStore;
|
|
227
|
+
getDownloadUrl(token: string): string;
|
|
152
228
|
private _pages;
|
|
153
229
|
private _resources;
|
|
154
230
|
private _navigationItems;
|
|
155
231
|
private _navigationGroups;
|
|
232
|
+
private _navigationGroupOptions;
|
|
156
233
|
private _maxContentWidth;
|
|
157
234
|
readonly colors: ColorManager;
|
|
158
235
|
static make(): Panel;
|
|
@@ -191,12 +268,44 @@ export declare class Panel {
|
|
|
191
268
|
login(page?: typeof Login): this;
|
|
192
269
|
auth(provider: AuthProvider): this;
|
|
193
270
|
key(key: string): this;
|
|
271
|
+
private _authGuard;
|
|
272
|
+
private _canAccess;
|
|
273
|
+
/**
|
|
274
|
+
* Session shared with the other panels that use the same guard (`'web'` by default): signing into one signs into all of
|
|
275
|
+
* them, and `canAccess()` decides who may enter each one. `null` gives this panel a session of its own.
|
|
276
|
+
* Panels sharing a guard must use the same key and the same auth provider.
|
|
277
|
+
*/
|
|
278
|
+
authGuard(guard: string | null): this;
|
|
279
|
+
getAuthGuard(): string | null;
|
|
280
|
+
/** Who may use this panel, e.g. `panel.canAccess((user) => user.role === 'admin')`. Checked on every request and at login. */
|
|
281
|
+
canAccess(rule: ((user: User, panel: Panel) => MaybePromise<boolean>) | null): this;
|
|
282
|
+
/** True if this user may use this panel: the panel's rule and the provider's `canAccessPanel()`, when set, must both allow it. */
|
|
283
|
+
userCanAccess(user: User): Promise<boolean>;
|
|
284
|
+
/** Name of the session cookie: per guard (shared), or per panel. */
|
|
285
|
+
getSessionCookieName(): string;
|
|
194
286
|
pages(pages: (typeof Page)[]): this;
|
|
195
287
|
/** Registers resources: their pages and navigation items. */
|
|
196
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[]>;
|
|
197
295
|
navigationItems(items: NavigationItem[]): this;
|
|
198
296
|
/** Order of navigation groups. */
|
|
199
|
-
|
|
297
|
+
/** Order of the navigation groups: labels, or `NavigationGroup.make('Shop').icon('o-shopping-cart').collapsible(false)`. */
|
|
298
|
+
private _darkMode;
|
|
299
|
+
private _darkModeForced;
|
|
300
|
+
private _defaultThemeMode;
|
|
301
|
+
/** `false`: light mode only (no switcher); `darkMode(true, true)`: dark mode only. */
|
|
302
|
+
darkMode(condition?: boolean, isForced?: boolean): this;
|
|
303
|
+
/** Mode used until the user picks one in the switcher: `'system'` (default), `'light'` or `'dark'`. */
|
|
304
|
+
defaultThemeMode(mode: 'light' | 'dark' | 'system'): this;
|
|
305
|
+
hasDarkMode(): boolean;
|
|
306
|
+
hasDarkModeForced(): boolean;
|
|
307
|
+
getDefaultThemeMode(): 'light' | 'dark' | 'system';
|
|
308
|
+
navigationGroups(groups: (string | NavigationGroup)[]): this;
|
|
200
309
|
private _plugins;
|
|
201
310
|
/** Applies a plugin: an object that configures the panel (resources, pages, render hooks, themes…). */
|
|
202
311
|
plugin(plugin: Plugin): this;
|
|
@@ -212,7 +321,14 @@ export declare class Panel {
|
|
|
212
321
|
private _authorizer;
|
|
213
322
|
/** Plugs in your own permission system: consulted before any policy, for every ability, page and widget. */
|
|
214
323
|
authorizeUsing(authorizer: Authorizer | null): this;
|
|
215
|
-
|
|
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. */
|
|
216
332
|
authorize(user: User | null, ability: string, subject?: {
|
|
217
333
|
resource?: unknown;
|
|
218
334
|
record?: unknown;
|
|
@@ -320,10 +436,68 @@ export declare class Panel {
|
|
|
320
436
|
page: typeof EditProfile;
|
|
321
437
|
isSimple: boolean;
|
|
322
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;
|
|
323
460
|
/** Mailer for password reset and verification emails. Defaults to the console. */
|
|
324
461
|
mailer(mailer: Mailer): this;
|
|
325
462
|
sendMail(message: MailMessage): Promise<void>;
|
|
326
463
|
getAuthKey(): string;
|
|
464
|
+
private _authSlugs;
|
|
465
|
+
private authSlug;
|
|
466
|
+
/** URL segment of the login page (`login` by default). */
|
|
467
|
+
loginRouteSlug(slug: string): this;
|
|
468
|
+
/** URL segment of the registration page (`register` by default). */
|
|
469
|
+
registrationRouteSlug(slug: string): this;
|
|
470
|
+
/** URL segments of the password reset pages: `<prefix>/<request>` and `<prefix>/<reset>` (`password-reset/request` and `password-reset/reset` by default). */
|
|
471
|
+
passwordResetRoutePrefix(prefix: string): this;
|
|
472
|
+
passwordResetRequestRouteSlug(slug: string): this;
|
|
473
|
+
passwordResetRouteSlug(slug: string): this;
|
|
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;
|
|
487
|
+
getAuthPaths(): {
|
|
488
|
+
login: string;
|
|
489
|
+
registration: string;
|
|
490
|
+
passwordResetRequest: string;
|
|
491
|
+
passwordReset: string;
|
|
492
|
+
logout: string;
|
|
493
|
+
emailVerificationPrompt: string;
|
|
494
|
+
emailVerification: string;
|
|
495
|
+
emailChangeVerification: string;
|
|
496
|
+
blockEmailChange: string;
|
|
497
|
+
setUpRequiredMultiFactor: string;
|
|
498
|
+
};
|
|
499
|
+
/** First URL segments used by the panel itself (never page slugs or tenant keys). */
|
|
500
|
+
getReservedSegments(): Set<string>;
|
|
327
501
|
getRegistrationUrl(): string;
|
|
328
502
|
getRequestPasswordResetUrl(): string;
|
|
329
503
|
getResetPasswordUrl(user: User, fingerprint: string): string;
|
|
@@ -335,6 +509,24 @@ export declare class Panel {
|
|
|
335
509
|
getVerifyEmailUrl(user: User): string;
|
|
336
510
|
checkVerifyEmailUrl(id: string, hash: string, expires: unknown, signature: unknown): boolean;
|
|
337
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;
|
|
338
530
|
getProfileUrl(): string | null;
|
|
339
531
|
/** Whether the user must still verify their email before using the panel. */
|
|
340
532
|
mustVerifyEmail(user: User | null): Promise<boolean>;
|
|
@@ -363,6 +555,41 @@ export declare class Panel {
|
|
|
363
555
|
getTenantProfilePage(): typeof EditTenantProfile | null;
|
|
364
556
|
tenantMenu(condition?: boolean): this;
|
|
365
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;
|
|
366
593
|
tenantMenuItems(items: Record<string, Action> | Action[]): this;
|
|
367
594
|
getTenantMenuItems(): Record<string, Action>;
|
|
368
595
|
/** Runs `fn` for a tenant: generated URLs (`getUrl`) and resource queries are scoped to it. */
|
|
@@ -395,9 +622,41 @@ export declare class Panel {
|
|
|
395
622
|
/** Tenant avatar: from the tenant provider (`getAvatarUrl`), otherwise its initials. */
|
|
396
623
|
getTenantAvatarUrl(tenant: unknown): string;
|
|
397
624
|
private _spa;
|
|
398
|
-
|
|
399
|
-
|
|
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;
|
|
400
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>;
|
|
401
660
|
getAvatarUrlFor(name: string): string;
|
|
402
661
|
/** Navigation groups: registered pages plus custom items, sorted, grouped in the declared order. */
|
|
403
662
|
/** Computed navigation badges: page URL → value. */
|
|
@@ -408,14 +667,9 @@ export declare class Panel {
|
|
|
408
667
|
label: string | null;
|
|
409
668
|
itemsId: string;
|
|
410
669
|
isActive: boolean;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
icon: string | null;
|
|
415
|
-
isActive: boolean;
|
|
416
|
-
badge: string | number | null;
|
|
417
|
-
badgeColorClasses: string[];
|
|
418
|
-
}[];
|
|
670
|
+
icon: string | null;
|
|
671
|
+
collapsible: boolean;
|
|
672
|
+
items: NavigationItemView[];
|
|
419
673
|
}[];
|
|
420
674
|
private sign;
|
|
421
675
|
logIn(res: Response, user: User, remember: boolean): void;
|
|
@@ -527,6 +781,63 @@ declare abstract class BasePage extends Component implements SchemaHost {
|
|
|
527
781
|
contentSchema(schema: Schema): Schema;
|
|
528
782
|
/** Render hook scopes of this page: its class (and its resource, for a resource page). */
|
|
529
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>;
|
|
530
841
|
}
|
|
531
842
|
/** A panel page, in the full layout (sidebar, header). */
|
|
532
843
|
export declare class Page extends BasePage {
|
|
@@ -540,9 +851,15 @@ export declare class Page extends BasePage {
|
|
|
540
851
|
static navigationSort: number;
|
|
541
852
|
static navigationBadge: string | number | null;
|
|
542
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;
|
|
543
858
|
static shouldRegisterNavigation: boolean;
|
|
544
859
|
/** Restricts the page to some users: returning `false` hides it from the menu and denies it (403). */
|
|
545
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;
|
|
546
863
|
static getTitle(): string;
|
|
547
864
|
static getNavigationLabel(): string;
|
|
548
865
|
/** Every request, including browser updates, requires an authenticated user: a snapshot kept after logout is not enough. */
|
|
@@ -556,8 +873,13 @@ export declare class Page extends BasePage {
|
|
|
556
873
|
}[];
|
|
557
874
|
getHeaderActions(): Action[];
|
|
558
875
|
getPageClasses(): string[];
|
|
559
|
-
/** Page sub-navigation; empty
|
|
876
|
+
/** Page sub-navigation: its cluster's pages, for a page in a cluster; empty otherwise. */
|
|
560
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
|
+
}[];
|
|
561
883
|
/** Sub-navigation position: `start` (column before the content), `end` (after) or `top` (tabs). Defaults to the resource's, then the panel's. */
|
|
562
884
|
static subNavigationPosition: 'start' | 'end' | 'top' | null;
|
|
563
885
|
getSubNavigationPosition(): 'start' | 'end' | 'top';
|
|
@@ -577,49 +899,33 @@ export declare class Page extends BasePage {
|
|
|
577
899
|
/** Widget grid of a schema: each widget is a child component, lazy-loaded by default. */
|
|
578
900
|
protected renderWidgetsGrid(widgets: (typeof Widget)[], columns: number | Record<string, number>, schemaKey: string): Promise<string>;
|
|
579
901
|
/** Page content: its `content` schema by default. */
|
|
580
|
-
protected renderContent(): Promise<string>;
|
|
581
|
-
mountedActions: {
|
|
582
|
-
name: string;
|
|
583
|
-
arguments: Record<string, unknown>;
|
|
584
|
-
context: Record<string, unknown>;
|
|
585
|
-
data?: Record<string, unknown>;
|
|
586
|
-
}[];
|
|
587
|
-
getActionMountContext(): Record<string, unknown>;
|
|
588
|
-
protected getActionRecord(): unknown;
|
|
589
|
-
/** Record targeted by an action mounted in a given context (table row…). */
|
|
590
|
-
protected resolveActionRecord(_context: Record<string, unknown>): Promise<unknown>;
|
|
591
|
-
/** Actions that can be mounted in a given context (page header by default). */
|
|
592
|
-
protected getMountableActions(context: Record<string, unknown>): Action[] | Promise<Action[]>;
|
|
593
902
|
/**
|
|
594
|
-
*
|
|
595
|
-
*
|
|
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`.
|
|
596
906
|
*/
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
private actionContext;
|
|
602
|
-
/** Form schema of a mounted action: key `mountedActionSchema<n>`, state under `mountedActions.<n>.data`. */
|
|
603
|
-
getMountedActionSchema(index: number): Promise<Schema | null>;
|
|
604
|
-
private renderActionModal;
|
|
605
|
-
/** Cancels a partial modal render prepared by a live field when the same request then runs an action. */
|
|
606
|
-
private cancelModalPartialRender;
|
|
607
|
-
/** Live field in a mounted action's form: runs `afterStateUpdated`, then only returns the affected modal. */
|
|
608
|
-
updatedMountedActions(_value: unknown, key: string | null): Promise<void>;
|
|
609
|
-
/** Calls a method exposed by a field, from that field's JavaScript (e.g. Select search). Does not re-render. */
|
|
610
|
-
callSchemaComponentMethod(componentKey: unknown, method: unknown, args?: unknown): Promise<unknown>;
|
|
611
|
-
/** A hidden action can be neither mounted nor run, even through a call forged by the browser. */
|
|
612
|
-
private assertActionVisible;
|
|
613
|
-
private syncActionModals;
|
|
614
|
-
/** False for a table component: its modals are rendered inside the table. */
|
|
615
|
-
protected rendersActionModalsInPage(): boolean;
|
|
616
|
-
renderActionModals(): Promise<string>;
|
|
617
|
-
/** Mounts an action: without a modal it runs immediately; with one, only the modals fragment is returned. */
|
|
618
|
-
mountAction(name: unknown, args?: unknown, _context?: unknown): Promise<void>;
|
|
619
|
-
unmountAction(_shouldCancelParentActions?: unknown): void;
|
|
620
|
-
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>;
|
|
621
911
|
render(): Promise<string>;
|
|
622
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
|
+
}
|
|
623
929
|
/**
|
|
624
930
|
* Dashboard: a widget grid, preceded by a filters form when the page defines `filtersFormSchema(schema)`.
|
|
625
931
|
* The filters are passed to widgets as `pageFilters`.
|
|
@@ -632,6 +938,7 @@ export declare class Dashboard extends Page {
|
|
|
632
938
|
/** Filters form state. Stays `null` while the page has no filters. */
|
|
633
939
|
filters: Record<string, unknown> | null;
|
|
634
940
|
/** Dashboard widgets. Defaults to the panel's (`panel.widgets([...])`). */
|
|
941
|
+
/** The panel's widgets, ordered by their `sort` (stable). */
|
|
635
942
|
getWidgets(): (typeof Widget)[];
|
|
636
943
|
/** Grid columns: a number (at the `lg` breakpoint), or one number per breakpoint. */
|
|
637
944
|
getColumns(): number | Record<string, number>;
|
|
@@ -641,16 +948,6 @@ export declare class Dashboard extends Page {
|
|
|
641
948
|
getWidgetData(): Record<string, unknown>;
|
|
642
949
|
protected renderContent(): Promise<string>;
|
|
643
950
|
}
|
|
644
|
-
/** In-memory rate limiter: `max` attempts per window, per key. Fine for a single instance; use a shared store when running several. */
|
|
645
|
-
export declare class Throttle {
|
|
646
|
-
private hits;
|
|
647
|
-
private max;
|
|
648
|
-
private windowMs;
|
|
649
|
-
constructor(max?: number, windowMs?: number);
|
|
650
|
-
/** Records an attempt; returns 0 if allowed, otherwise the number of seconds to wait. */
|
|
651
|
-
hit(key: string, now?: number): number;
|
|
652
|
-
clear(key: string): void;
|
|
653
|
-
}
|
|
654
951
|
/** Page with the simple layout (login, registration…). */
|
|
655
952
|
export declare abstract class SimplePage extends BasePage {
|
|
656
953
|
abstract getTitle(): string;
|
|
@@ -725,8 +1022,17 @@ export declare class EditProfile extends SimplePage {
|
|
|
725
1022
|
boot(): Promise<void>;
|
|
726
1023
|
getTitle(): string;
|
|
727
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>;
|
|
728
1028
|
formSchema(schema: Schema): Schema;
|
|
1029
|
+
/** Fields of the form: name, email, new password, its confirmation, current password. */
|
|
1030
|
+
protected getFormComponents(): SchemaComponent[];
|
|
729
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;
|
|
730
1036
|
save(): Promise<void>;
|
|
731
1037
|
}
|
|
732
1038
|
/** Tenant registration page: the subclass provides its label and form (`formSchema`). */
|
|
@@ -763,14 +1069,42 @@ export declare abstract class EditTenantProfile extends Page {
|
|
|
763
1069
|
}
|
|
764
1070
|
export declare class Login extends SimplePage {
|
|
765
1071
|
static callable: string[];
|
|
1072
|
+
/** Signed token naming the user undertaking the multi-factor challenge: set by the server only. */
|
|
1073
|
+
static locked: string[];
|
|
766
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[];
|
|
767
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;
|
|
768
1084
|
getTitle(): string;
|
|
769
1085
|
getHeading(): string;
|
|
770
1086
|
getSubheading(): string | null;
|
|
1087
|
+
getSchema(name: string): Schema;
|
|
771
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;
|
|
772
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>;
|
|
773
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;
|
|
774
1108
|
}
|
|
775
1109
|
/** Whether the host component's user has this ability on the component's resource (or relation manager). Use it in your actions' `authorize()`. */
|
|
776
1110
|
export declare const can: (livewire: unknown, ability: string, record?: unknown) => Promise<boolean> | boolean;
|
|
@@ -784,6 +1118,12 @@ export declare abstract class Resource {
|
|
|
784
1118
|
static navigationGroup: string | null;
|
|
785
1119
|
static navigationSort: number;
|
|
786
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;
|
|
787
1127
|
static adapter: DataAdapter;
|
|
788
1128
|
/** In a multi-tenant panel, whether records belong to each tenant (`true` by default). */
|
|
789
1129
|
static isScopedToTenant: boolean;
|
|
@@ -809,8 +1149,9 @@ export declare abstract class Resource {
|
|
|
809
1149
|
static widgets: (typeof Widget)[];
|
|
810
1150
|
/** Widgets shown above the table on the list page. */
|
|
811
1151
|
static headerWidgets: (typeof Widget)[];
|
|
812
|
-
/** Relation managers shown below the edit form. */
|
|
813
|
-
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)[];
|
|
814
1155
|
/** Tabs of the list page. */
|
|
815
1156
|
static tabs(): Record<string, Tab>;
|
|
816
1157
|
/** Simple resource: a single page, with create and edit in modals. */
|
|
@@ -819,7 +1160,7 @@ export declare abstract class Resource {
|
|
|
819
1160
|
static getTitleCasePluralModelLabel(): string;
|
|
820
1161
|
static getUrl(page?: string, record?: string | number): string;
|
|
821
1162
|
/** Related records pages: `{ comments: ManagePostComments }` is served at `<slug>/{record}/comments`. */
|
|
822
|
-
static relatedPages: Record<string, typeof ManageRelatedRecords>;
|
|
1163
|
+
static relatedPages: Record<string, typeof ManageRelatedRecords | typeof EditRecord | typeof ViewRecord>;
|
|
823
1164
|
/** Pages listed in a record's sub-navigation, in order: `['view', 'edit', 'comments']`. Empty: no sub-navigation. */
|
|
824
1165
|
static recordSubNavigation: string[];
|
|
825
1166
|
/** Record sub-navigation: one tab per declared page, the current page's tab active. */
|
|
@@ -837,6 +1178,10 @@ export declare abstract class Resource {
|
|
|
837
1178
|
/** Columns searched by global search; defaults to the title attribute. Empty array: the resource is not searchable. */
|
|
838
1179
|
static globallySearchableAttributes: string[] | null;
|
|
839
1180
|
static getGloballySearchableAttributes(): string[];
|
|
1181
|
+
/** `false`: the resource is left out of global search. */
|
|
1182
|
+
static isGloballySearchable: boolean;
|
|
1183
|
+
/** `false`: the search term is matched as a whole instead of word by word. */
|
|
1184
|
+
static shouldSplitGlobalSearchTerms: boolean;
|
|
840
1185
|
static canGloballySearch(): boolean;
|
|
841
1186
|
/** Position of this resource's group in the results; without one, after the others in registration order. */
|
|
842
1187
|
static globalSearchSort: number | null;
|
|
@@ -845,6 +1190,11 @@ export declare abstract class Resource {
|
|
|
845
1190
|
static getGlobalSearchResultTitle(record: any): string;
|
|
846
1191
|
/** Details shown under the title: `{ Author: 'Jane' }`. Empty values are skipped. */
|
|
847
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[]>;
|
|
848
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. */
|
|
849
1199
|
static getGlobalSearchResultUrl(record: any, user: User | null): Promise<string | null>;
|
|
850
1200
|
static getGlobalSearchResults(search: string, user: User | null): Promise<GlobalSearchResult[]>;
|
|
@@ -856,26 +1206,44 @@ export declare abstract class Resource {
|
|
|
856
1206
|
static getPages(): Record<string, typeof Page>;
|
|
857
1207
|
private static buildPages;
|
|
858
1208
|
}
|
|
859
|
-
/** Sub-navigation tab of a page. */
|
|
1209
|
+
/** Sub-navigation tab of a page; items with a `group` are gathered under its label. */
|
|
860
1210
|
export type SubNavigationItem = {
|
|
861
1211
|
label: string;
|
|
862
1212
|
icon: string | null;
|
|
863
1213
|
url: string;
|
|
864
1214
|
active: boolean;
|
|
1215
|
+
group?: string | null;
|
|
1216
|
+
badge?: string | number | null;
|
|
1217
|
+
badgeColor?: string;
|
|
865
1218
|
};
|
|
866
|
-
|
|
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;
|
|
1229
|
+
};
|
|
1230
|
+
/** The cluster a page belongs to: its own, or its resource's. */
|
|
1231
|
+
export declare const clusterOf: (page: unknown) => typeof Cluster | null;
|
|
1232
|
+
type ColumnSpan = number | 'full' | Record<string, number | 'full'>;
|
|
867
1233
|
/** Widget: a component displayed in a page's widget grid. */
|
|
868
1234
|
export declare abstract class Widget extends Component {
|
|
869
1235
|
static componentName: string;
|
|
870
|
-
/** Width in the widget grid: a number of columns
|
|
1236
|
+
/** Width in the widget grid: a number of columns or `'full'` (from `lg`), or a span per breakpoint (`{ md: 2, xl: 3 }`). */
|
|
871
1237
|
static columnSpan: ColumnSpan;
|
|
1238
|
+
/** Position on the dashboard (lower first); widgets without a sort keep their order, after the sorted ones. */
|
|
1239
|
+
static sort: number | null;
|
|
872
1240
|
/** Loaded when it becomes visible. */
|
|
873
1241
|
static isLazy: boolean;
|
|
874
1242
|
get panel(): Panel;
|
|
875
1243
|
/** Restricts the widget to some users: returning `false` removes it from the page and refuses to load it. */
|
|
876
1244
|
static canView(_user: User): boolean | Promise<boolean>;
|
|
877
1245
|
boot(): Promise<void>;
|
|
878
|
-
/** Grid column classes and style
|
|
1246
|
+
/** Grid column classes and style: at the `lg` breakpoint, or per breakpoint. */
|
|
879
1247
|
protected gridColumn(): {
|
|
880
1248
|
classes: string[];
|
|
881
1249
|
style: string;
|
|
@@ -1086,6 +1454,10 @@ export declare abstract class StatsOverviewWidget extends Widget {
|
|
|
1086
1454
|
/** Polling interval, or `null` to disable polling. */
|
|
1087
1455
|
static pollingInterval: string | null;
|
|
1088
1456
|
abstract getStats(): Stat[] | Promise<Stat[]>;
|
|
1457
|
+
/** Heading shown above the stats. */
|
|
1458
|
+
protected getHeading(): string | null;
|
|
1459
|
+
/** Description under the heading. */
|
|
1460
|
+
protected getDescription(): string | null;
|
|
1089
1461
|
/** Row columns: three, or four when the number of stats fits better. */
|
|
1090
1462
|
protected getColumns(count: number): number;
|
|
1091
1463
|
render(): Promise<string>;
|
|
@@ -1102,6 +1474,10 @@ export type ChartData = {
|
|
|
1102
1474
|
* has changed (checksum).
|
|
1103
1475
|
*/
|
|
1104
1476
|
export declare abstract class ChartWidget extends Widget {
|
|
1477
|
+
/** Empty state shown when `getData()` returns nothing. */
|
|
1478
|
+
protected getEmptyStateHeading(): string;
|
|
1479
|
+
protected getEmptyStateDescription(): string | null;
|
|
1480
|
+
protected getEmptyStateIcon(): string | null;
|
|
1105
1481
|
static callable: string[];
|
|
1106
1482
|
static locked: string[];
|
|
1107
1483
|
/** Polling interval, or `null` to disable polling. */
|
|
@@ -1209,6 +1585,27 @@ export declare class EmbeddedTable extends SchemaComponent {
|
|
|
1209
1585
|
static make(): EmbeddedTable;
|
|
1210
1586
|
toHtml(): Promise<string>;
|
|
1211
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
|
+
}
|
|
1212
1609
|
/** List page tab: restricts the table query and can show a badge. */
|
|
1213
1610
|
export declare class Tab {
|
|
1214
1611
|
private _label;
|
|
@@ -1222,6 +1619,13 @@ export declare class Tab {
|
|
|
1222
1619
|
badge(resolver: () => number | string | null | Promise<number | string | null>): this;
|
|
1223
1620
|
badgeColor(color: string): this;
|
|
1224
1621
|
query(modifier: (query: any) => void): this;
|
|
1622
|
+
private _icon;
|
|
1623
|
+
private _iconPosition;
|
|
1624
|
+
/** Icon of the tab, before its label (or after, with `iconPosition('after')`). */
|
|
1625
|
+
icon(name: string | null): this;
|
|
1626
|
+
iconPosition(position: 'before' | 'after'): this;
|
|
1627
|
+
getIcon(): string | null;
|
|
1628
|
+
getIconPosition(): 'before' | 'after';
|
|
1225
1629
|
getLabel(key: string): string;
|
|
1226
1630
|
hasBadge(): boolean;
|
|
1227
1631
|
getBadge(): Promise<number | string | null>;
|
|
@@ -1234,6 +1638,8 @@ export declare class Tab {
|
|
|
1234
1638
|
export declare class ListRecords extends Page implements TableHost {
|
|
1235
1639
|
static callable: string[];
|
|
1236
1640
|
static resource: typeof Resource;
|
|
1641
|
+
/** In a cluster, the list page shows the cluster's sub-navigation. */
|
|
1642
|
+
getSubNavigation(): SubNavigationItem[] | Promise<SubNavigationItem[]>;
|
|
1237
1643
|
tableSearch: string;
|
|
1238
1644
|
tableSort: string | null;
|
|
1239
1645
|
tableColumnSearches: Record<string, string>;
|
|
@@ -1253,16 +1659,29 @@ export declare class ListRecords extends Page implements TableHost {
|
|
|
1253
1659
|
toggleTableReordering(): Promise<void>;
|
|
1254
1660
|
/** Saves the new order: only keys belonging to the table are kept, each gets its position. */
|
|
1255
1661
|
reorderTable(order: unknown, _draggedRecordKey?: unknown): Promise<void>;
|
|
1662
|
+
/** Key of this table's persisted state: per page class (and resource or relationship). */
|
|
1663
|
+
protected tableSessionKey(part: string): string;
|
|
1664
|
+
/** Session of the persisted table state: the browser (its CSRF token) and the signed-in user. */
|
|
1665
|
+
private tableSession;
|
|
1666
|
+
dehydrate(): Promise<void>;
|
|
1667
|
+
/** Restores the persisted table state; every value is sanitized like browser input. */
|
|
1668
|
+
private restoreTableSession;
|
|
1256
1669
|
mount(_params?: Record<string, unknown>): Promise<void>;
|
|
1257
1670
|
/** Filters form: one group per filter, bound to `tableDeferredFilters.<filter>`. */
|
|
1258
1671
|
tableFiltersFormSchema(schema: Schema): Schema;
|
|
1672
|
+
/** Applied state of every declared filter, sanitized from the given (browser) state. */
|
|
1673
|
+
protected sanitizeTableFilters(source: Record<string, unknown> | undefined): Record<string, FilterState>;
|
|
1674
|
+
/** Live filters (`deferFilters(false)`): a changed field applies at once. */
|
|
1675
|
+
updatedTableFilters(value: unknown, key: string | null): Promise<void>;
|
|
1259
1676
|
applyTableFilters(): void;
|
|
1260
1677
|
/** Removes an active filter indicator: the field is cleared from both the applied state and the form. */
|
|
1261
|
-
removeTableFilter(filterName: unknown, field?: unknown): void
|
|
1678
|
+
removeTableFilter(filterName: unknown, field?: unknown): Promise<void>;
|
|
1262
1679
|
/** Live field in the filters form (e.g. a rule's operator changed): runs its `afterStateUpdated`. */
|
|
1263
1680
|
updatedTableDeferredFilters(_value: unknown, key: string | null): Promise<void>;
|
|
1681
|
+
private runFilterFieldHook;
|
|
1264
1682
|
resetTableFiltersForm(): void;
|
|
1265
|
-
|
|
1683
|
+
/** "Remove all": resets every removable filter (to its empty state, not its default), the search and the column searches. */
|
|
1684
|
+
removeTableFilters(): Promise<void>;
|
|
1266
1685
|
applyTableColumnManager(state?: unknown, _wasReordered?: unknown): void;
|
|
1267
1686
|
resetTableColumnManager(): void;
|
|
1268
1687
|
activeTab: string | null;
|
|
@@ -1292,10 +1711,23 @@ export declare class ListRecords extends Page implements TableHost {
|
|
|
1292
1711
|
updateTableColumnState(columnName: unknown, recordKey: unknown, input: unknown): Promise<unknown>;
|
|
1293
1712
|
/** Selected keys: the sent list, or, in "select all" mode, every record of the current query except deselected ones. */
|
|
1294
1713
|
getSelectedRecordKeys(): Promise<string[]>;
|
|
1714
|
+
/**
|
|
1715
|
+
* Runs `process` for each selected record, loaded in chunks of `chunkSelectedRecords()`. Records failing the action's
|
|
1716
|
+
* `authorizeIndividualRecords()` are skipped; `process` reports a failure by returning `false` or throwing. Then sends the
|
|
1717
|
+
* action's success notification, or its failure notification with the number of records skipped and failed.
|
|
1718
|
+
*/
|
|
1719
|
+
processSelectedRecords(action: Action, process: (record: any) => unknown | Promise<unknown>): Promise<{
|
|
1720
|
+
total: number;
|
|
1721
|
+
success: number;
|
|
1722
|
+
unauthorized: number;
|
|
1723
|
+
failed: number;
|
|
1724
|
+
}>;
|
|
1295
1725
|
deselectAllTableRecords(): void;
|
|
1296
1726
|
getTable(): Table;
|
|
1297
1727
|
renderTable(): Promise<string>;
|
|
1728
|
+
protected renderTableEmptyStateActions(ctx: any): Promise<string[]>;
|
|
1298
1729
|
/** Table header actions (none on a list page: they go in the page header). */
|
|
1730
|
+
protected renderTableToolbarActions(ctx: any): Promise<string[]>;
|
|
1299
1731
|
protected renderTableHeaderActions(ctx: any): Promise<string[]>;
|
|
1300
1732
|
sortTable(column?: string | null, direction?: string | null): void;
|
|
1301
1733
|
gotoPage(page: number, _pageName?: string): void;
|
|
@@ -1332,6 +1764,19 @@ export declare class RelationManager extends ListRecords {
|
|
|
1332
1764
|
static table(table: Table): Table;
|
|
1333
1765
|
static form(schema: Schema): Schema;
|
|
1334
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>;
|
|
1335
1780
|
/** Owner record key: set on mount by the page, never writable from the browser. */
|
|
1336
1781
|
ownerRecord: string;
|
|
1337
1782
|
static locked: string[];
|
|
@@ -1339,14 +1784,30 @@ export declare class RelationManager extends ListRecords {
|
|
|
1339
1784
|
/** What ListRecords expects from a resource, provided here by the relation manager. */
|
|
1340
1785
|
get resource(): any;
|
|
1341
1786
|
getTable(): Table;
|
|
1342
|
-
/** Attaches an existing record to the owner. */
|
|
1343
|
-
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>;
|
|
1344
1797
|
/** Detaches records from the owner without deleting them. */
|
|
1345
1798
|
static detach(_ownerKey: string, _recordKeys: string[]): Promise<void>;
|
|
1346
1799
|
/** Attachable records matching the search: [key, label]. */
|
|
1347
1800
|
static searchAttachable(_ownerKey: string, _search: string): Promise<[string, string][]>;
|
|
1348
1801
|
/** Label of an attachable record, or `null` if it is not attachable (also used as validation). */
|
|
1349
1802
|
static attachableLabel(_ownerKey: string, _recordKey: string): Promise<string | null>;
|
|
1803
|
+
/** Sets the owner of an existing record (its foreign key). */
|
|
1804
|
+
static associate(_ownerKey: string, _recordKey: string): Promise<void>;
|
|
1805
|
+
/** Clears the owner of records (their foreign key), without deleting them. */
|
|
1806
|
+
static dissociate(_ownerKey: string, _recordKeys: string[]): Promise<void>;
|
|
1807
|
+
/** Associable records matching the search: [key, label]. */
|
|
1808
|
+
static searchAssociable(_ownerKey: string, _search: string): Promise<[string, string][]>;
|
|
1809
|
+
/** Label of an associable record, or `null` if it is not associable (also used as validation). */
|
|
1810
|
+
static associableLabel(_ownerKey: string, _recordKey: string): Promise<string | null>;
|
|
1350
1811
|
getHeaderActions(): Action[];
|
|
1351
1812
|
placeholder(): string;
|
|
1352
1813
|
/** A relation manager has its own render hook locations, not the list page's. */
|
|
@@ -1402,6 +1863,20 @@ declare abstract class ResourceFormPage extends Page {
|
|
|
1402
1863
|
updatedData(_value: unknown, key: string | null): Promise<void>;
|
|
1403
1864
|
static resource: typeof Resource;
|
|
1404
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>;
|
|
1405
1880
|
get resource(): typeof Resource;
|
|
1406
1881
|
getDataAdapter(): DataAdapter;
|
|
1407
1882
|
getRecordKey(): string | null;
|
|
@@ -1425,13 +1900,21 @@ declare abstract class ResourceFormPage extends Page {
|
|
|
1425
1900
|
/** Where "Cancel" goes when the browser has no history: the list page, unless the page decides otherwise. */
|
|
1426
1901
|
protected getCancelUrl(): string;
|
|
1427
1902
|
protected cancelAction(): Action;
|
|
1903
|
+
/** Before the form is filled (with defaults, or with the record on the edit page). */
|
|
1904
|
+
protected beforeFill(): void | Promise<void>;
|
|
1905
|
+
/** After the form is filled. */
|
|
1906
|
+
protected afterFill(): void | Promise<void>;
|
|
1907
|
+
/** Before the form is validated, when it is submitted. */
|
|
1908
|
+
protected beforeValidate(): void | Promise<void>;
|
|
1909
|
+
/** After the form is validated successfully, before the data is transformed and saved. */
|
|
1910
|
+
protected afterValidate(): void | Promise<void>;
|
|
1428
1911
|
/** Validates the form; on error, sets the errors on the component and notifies the browser. */
|
|
1429
1912
|
protected validateForm(): Promise<boolean>;
|
|
1430
1913
|
}
|
|
1431
1914
|
/** Record creation page. */
|
|
1432
1915
|
export declare class CreateRecord extends ResourceFormPage {
|
|
1433
1916
|
static callable: string[];
|
|
1434
|
-
mount(): void
|
|
1917
|
+
mount(): Promise<void>;
|
|
1435
1918
|
getOperation(): 'create';
|
|
1436
1919
|
getSubmitMethod(): string;
|
|
1437
1920
|
getTitle(): string;
|
|
@@ -1461,9 +1944,21 @@ export declare class DeleteAction {
|
|
|
1461
1944
|
/** On an edit page, deletes the page's record and returns to the list. On a table row, deletes the row. */
|
|
1462
1945
|
static make(): Action;
|
|
1463
1946
|
}
|
|
1464
|
-
/** Replicate action: an `Action` with a list of attributes not to copy. */
|
|
1947
|
+
/** Replicate action: an `Action` with a list of attributes not to copy, and hooks around the saving of the copy. */
|
|
1465
1948
|
export type ReplicateActionInstance = Action & {
|
|
1466
1949
|
excludeAttributes(attributes: string[]): ReplicateActionInstance;
|
|
1950
|
+
/** Runs before the copy is saved, with its attributes (`replica`, changeable) and the modal form's data. */
|
|
1951
|
+
beforeReplicaSaved(callback: (ctx: {
|
|
1952
|
+
replica: Record<string, unknown>;
|
|
1953
|
+
data: Record<string, unknown>;
|
|
1954
|
+
record: unknown;
|
|
1955
|
+
}) => unknown): ReplicateActionInstance;
|
|
1956
|
+
/** Runs after the copy is saved, with the saved record. */
|
|
1957
|
+
afterReplicaSaved(callback: (ctx: {
|
|
1958
|
+
replica: unknown;
|
|
1959
|
+
data: Record<string, unknown>;
|
|
1960
|
+
record: unknown;
|
|
1961
|
+
}) => unknown): ReplicateActionInstance;
|
|
1467
1962
|
};
|
|
1468
1963
|
/** Duplicates a table row: the copy takes the record's attributes, except its key, timestamps and excluded ones. */
|
|
1469
1964
|
export declare class ReplicateAction {
|
|
@@ -1495,7 +1990,20 @@ export declare class DetachAction {
|
|
|
1495
1990
|
export declare class DetachBulkAction {
|
|
1496
1991
|
static make(): Action;
|
|
1497
1992
|
}
|
|
1993
|
+
/** Gives an existing record to the owner of a one-to-many relation manager (its foreign key). */
|
|
1994
|
+
export declare class AssociateAction {
|
|
1995
|
+
static make(): Action;
|
|
1996
|
+
}
|
|
1997
|
+
/** Removes a row from the owner (clears its foreign key) without deleting it. */
|
|
1998
|
+
export declare class DissociateAction {
|
|
1999
|
+
static make(): Action;
|
|
2000
|
+
}
|
|
2001
|
+
/** Dissociates the selected rows. */
|
|
2002
|
+
export declare class DissociateBulkAction {
|
|
2003
|
+
static make(): Action;
|
|
2004
|
+
}
|
|
1498
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. */
|
|
1499
2007
|
export declare class RestoreAction {
|
|
1500
2008
|
static make(): Action;
|
|
1501
2009
|
}
|
|
@@ -1503,10 +2011,21 @@ export declare class RestoreAction {
|
|
|
1503
2011
|
export declare class ForceDeleteAction {
|
|
1504
2012
|
static make(): Action;
|
|
1505
2013
|
}
|
|
1506
|
-
/**
|
|
2014
|
+
/**
|
|
2015
|
+
* Deletes the records selected in a table. Without `authorizeIndividualRecords()`, the selection is deleted in one query;
|
|
2016
|
+
* with it, record by record, and the notification reports the records that could not be deleted.
|
|
2017
|
+
*/
|
|
1507
2018
|
export declare class DeleteBulkAction {
|
|
1508
2019
|
static make(): Action;
|
|
1509
2020
|
}
|
|
2021
|
+
/** Restores the soft-deleted records selected in a table, each one checked with the `restore` ability. */
|
|
2022
|
+
export declare class RestoreBulkAction {
|
|
2023
|
+
static make(): Action;
|
|
2024
|
+
}
|
|
2025
|
+
/** Permanently deletes the soft-deleted records selected in a table, each one checked with the `forceDelete` ability. */
|
|
2026
|
+
export declare class ForceDeleteBulkAction {
|
|
2027
|
+
static make(): Action;
|
|
2028
|
+
}
|
|
1510
2029
|
/** Record edit page. */
|
|
1511
2030
|
export declare class EditRecord extends ResourceFormPage {
|
|
1512
2031
|
static callable: string[];
|
|
@@ -1519,7 +2038,19 @@ export declare class EditRecord extends ResourceFormPage {
|
|
|
1519
2038
|
*/
|
|
1520
2039
|
previousUrl: string;
|
|
1521
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;
|
|
1522
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>;
|
|
1523
2054
|
static locked: string[];
|
|
1524
2055
|
_record: any;
|
|
1525
2056
|
mount(params: Record<string, unknown>): Promise<void>;
|
|
@@ -1574,6 +2105,13 @@ export declare class ViewRecord extends Page {
|
|
|
1574
2105
|
getActionMountContext(): Record<string, unknown>;
|
|
1575
2106
|
protected getActionRecord(): unknown;
|
|
1576
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;
|
|
1577
2115
|
contentSchema(schema: Schema): Schema;
|
|
1578
2116
|
/** Defaults to a link to the edit page. */
|
|
1579
2117
|
getHeaderActions(): Action[];
|
|
@@ -1586,6 +2124,17 @@ export declare class ViewRecord extends Page {
|
|
|
1586
2124
|
getSubNavigation(): SubNavigationItem[];
|
|
1587
2125
|
getPageClasses(): string[];
|
|
1588
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;
|
|
1589
2138
|
/** Sidebar: logo, tenant menu, search, navigation and, without a topbar, the bell and user menu. */
|
|
1590
2139
|
export declare class Sidebar extends Component {
|
|
1591
2140
|
static callable: string[];
|
|
@@ -1620,5 +2169,6 @@ export declare const discoverPages: (directory: string | URL) => Promise<(typeof
|
|
|
1620
2169
|
export declare const discoverWidgets: (directory: string | URL) => Promise<(typeof Widget)[]>;
|
|
1621
2170
|
/** Panels default-exported (`export default Panel.make()…`) by a folder's modules. */
|
|
1622
2171
|
export declare function discoverPanels(directory: string | URL): Promise<Panel[]>;
|
|
1623
|
-
/** Express router for a panel: assets, runtime endpoint, authentication, tenants, protected pages. */
|
|
1624
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;
|