Operations
Overview
WebBlocks CMS includes install-level operational tools for updates, backups, and site transfer packages.
Settings also lives under the admin System navigation because it controls install-level project identity, locale, timezone, privacy, version, and environment settings.
Admin -> System -> Block Types also serves as an install-level catalog inspection screen. Its Support filter is for block capability and content-source metadata, while its Usage filter is for actual live usage counts from the blocks table so admins can review used versus unused block type rows.
Admin -> System -> Visitor Reports provides privacy-safe traffic reporting next to install-level system settings.
Maintenance contains the consolidated overview alongside the existing operational tools:
- System Health
- Search Rebuild
- Backups
- Cleanup
- Export / Import
- Update
System Health
Since CMS 1.98.0, Maintenance → System Health provides one place to review the installation and its sites. For users with System Health access, the Dashboard shows a compact summary of checks needing action, worth reviewing, or not yet verified, with a link to the full overview. Zero counts show that no issues were found in the available checks. The page starts with counts and the most urgent items, followed by cards for System, Sites, Backups, Scheduled work, Storage, Search, Plugins, and Updates. Each item explains the next step and links to the existing tool. Technical details, recent backup/restore/update outcomes, and the existing System Information values are folded by default.
The Scheduled notifications card includes the detailed scheduler notice, the effect on pending email, UTC times for the last scheduled heartbeat and completed notification run, setup instructions, and a link to the installation guide. Scheduling Review links open this section and keep the selected site filter. Site-operation managers who cannot access System Health retain the detailed, site-scoped Scheduler card on their Dashboard and in site Contact settings. The Panel notifications inbox summary remains on the Dashboard.
Statuses distinguish healthy, review needed, urgent, not yet verified, and not applicable. An unavailable check is not reported as healthy. Search checks compare eligible published page/locale pairs with index coverage; they do not certify text freshness. Backup checks cover recent successful records and archive availability; they do not certify restore integrity. Update readiness is separate from installation health, and release availability uses a recent cached update result. Open Updates to request a fresh release check.
Site checks are cached for five minutes; the System Health screen reads recorded scheduler evidence on each visit. Dashboard, Backups and Cleanup show the last completed report with its measurement time. They do not parse backup SQL while opening. Missing reports are unverified, and old or invalidated reports are explicitly marked stale. Settings remain available before the first report. Cleanup’s Storage and history and Cleanup actions tabs explain unavailable reports and offer Refresh checks directly; a refresh returns to the same tab. The refresh button shows activity while submitting, and the result confirms completion, reports that another refresh is already running, or shows a failure message. System Health refreshes retain the selected site filter.
Maintenance report cache entries contain versioned arrays and scalar values, so installations can keep cache object deserialization disabled. Refresh checks confirms success only after reading back the exact snapshot it wrote. A cache verification failure shows an error and attempts to retain the previous completed report; missing retention decisions on Backups are shown as unavailable previews rather than schema-update requirements. After a product update, the first report for that version is generated by Refresh checks or the scheduler.
The Laravel scheduler runs php artisan system:reports:refresh every five minutes. Refresh checks on System Health can also run this work manually. Each invocation validates at most eight existing recovery-floor backup archives, then updates the shared report; a large installation may need several invocations to finish initial validation. New backups and uploads record validation when created. Pending validation withholds cleanup eligibility in the panel report. A functioning scheduler is needed for automatic freshness; opening a page does not start a synchronous refresh. Cached reports never replace the current safety checks performed before cleanup or restore. The last completed report is retained for up to seven days and shown as stale after five minutes. The site filter narrows site-related checks while retaining installation-wide checks. The overview does not run cleanup, rebuild search, start backups, install updates, or create scheduler evidence. Existing Sites and operational maintenance pages remain available. From CMS 1.98.1, the separate System Information screen and sidebar link are removed; its nine runtime information fields remain in System Health → System details, and /webadmin/system/information redirects to System Health with the same System access requirement. Help is a direct documentation link to https://cms.webblocksui.com/docs; plugin destinations formerly contributed to Help remain reachable under System.
The read-only Internal API endpoint is GET /webadmin/api/system/health, optionally with site_id. It requires explicitly granted system-health.read on an unrestricted system token whose active creator currently has System access. Personal and site-scoped tokens cannot read installation-wide health. Responses use controlled messages and normalized outcomes rather than exception text, operation logs, archive paths, or plugin secrets. See Internal Content API for the response contract.
Visitor Reports
Visitor Reports provides operational summaries of public page activity. Basic page-view records do not require consent-based identifiers. Records include reduced referrer, campaign, device, and bot information:
- referrers are normalized to host/domain plus
direct,internal, orexternaltype; full referrer URLs are not stored or shown - empty referrers are grouped as
Direct / Unknown - UTM source, medium, and campaign are stored as normalized values only; the full query string is not stored
- user-agent strings are reduced at request time to device category (
desktop,mobile,tablet,bot, orunknown) plus coarse browser/OS families where available; full user-agent strings are not stored - known crawler and bot user-agent patterns are counted as bot page views and remain visible in the report instead of being silently removed
Unique visitors, sessions, entry pages, and average pages per session require consent-based session tracking. When a date/site/locale/traffic filter contains page views but no usable session or visitor identifier, the UI shows Not tracked instead of 0. Older rows that cannot be backfilled safely remain Unknown, Direct / Unknown, or Not tracked depending on which aggregate fields exist.
Site-scoped authorization still applies: super admins can see all sites, while site admins and editors can see only assigned site data. The site filter is limited to the user's accessible sites.
System Updates
From CMS 1.105.26, the progress dialog shows a translated current phase while execution is confirmed. Its title identifies the target version without implying that work is still running after completion. When tracking stops or the result becomes terminal, the phase, progress indicator and waiting advice disappear together; the elapsed time and result remain visible. Confirmed success returns to Updates automatically. Failure or restored-backup results stay available for review. Unknown phase identifiers use a readable fallback instead of a translation key.
System Updates checks the running CMS code version against the configured update service.
The update screen can report states such as:
- update available
- up to date
- local/source version newer than the latest published release
- incompatible update available
- no releases found
- update server unavailable
- invalid or unsupported response
The in-app update flow is one click. A single Update to X action downloads the release package, takes an automatic pre-update backup, applies protected-path rules, runs required package update migrations, clears caches, verifies the applied WebBlocks CMS code version against the target release, records the update run, and persists the installed version. If the apply fails, the updater automatically restores the pre-update backup and records the run with the restored status; if the automatic restore also fails, the run is recorded as failed with both error trails. For manual recovery, the pre-update backup remains on the Backups screen for download and restore, and php artisan system:backup:restore performs a CLI restore.
Automatic backup cleanup
Maintenance → Cleanup owns install-wide retention policies. The lifecycle
controls below are available in CMS 1.105.12.
| Backup type | Default minimum age | Latest successful archives protected |
|---|---|---|
| Pre-update | 14 days | 5 |
| Restore safety | 14 days | 2 |
| Content apply | 7 days | 3 |
| Manual | 30 days; policy cleanup separately enabled | 3 |
| Uploaded | Indefinite unless explicitly given an expiration | — |
Existing saved policies, including 30-day restore-safety retention, remain unchanged. Age and count protection apply together. Only available, structurally validated, distinct physical archives count toward a type's successful floor. An expired backup can remain protected by that floor. Failed/missing archives do not replace valid recovery points. Running backups and recovery points associated with recent failed/restored updates remain protected.
Use the lock/settings icon on Backups to manage an archive's Protected flag
and its policy, never or expires mode. Protection blocks single, forced and
bulk deletion until removed. Indefinite mode prevents automatic expiration but
still permits explicit deletion. Choosing a date explicitly opts that archive
into cleanup, including manual and uploaded archives; protection and successful
minimum counts still apply. Dates follow the application timezone. The manual
creation dialog records the selected protection/retention before capturing SQL.
The optional budget is measured in GiB (1024³ bytes), defaults to unset, warns at 80% and reports an overrun at 100%. It never overrides protection or retention. Backups and Cleanup show total physical storage, explicitly protected bytes, eligible bytes, estimated storage after cleanup and any remaining overrun. Unregistered files count toward disk usage but are never automatically selected. Partial/unavailable measurements are explicit. A shared archive is not counted multiple times, and deleting a record never unlinks another record's file.
Storage explanation (CMS 1.105.13): the Backup retention card in Cleanup’s Storage and history tab and the Backups list adds a reason table. It shows backup record counts, distinct physical file counts and measured bytes for minimum-count protection, retention periods, explicit protection, indefinite retention, disabled manual cleanup and other current decisions. Eligible archives appear separately. Age and minimum-count rules both protect backups; “keep the latest N” is a floor, not a cap on the total number retained.
Shared archive files contribute bytes once under the first applicable reason in
the table. Record counts still follow each record’s status, so a reason may have
records without additional physical bytes. Other files in backup storage,
including lock or support files, appear separately and are never automatically
removed. A partial scan shows known archive sizes with unconfirmed totals and
other-file sizes marked unavailable. The existing backup-cleanup API includes
the same breakdown at preview.overview.reason_summary.
Backup cleanup runs daily at 03:30 under the existing enabled setting.
The package does not invoke backup cleanup after a successful update; a working
host scheduler is required for its automatic execution. Preview with
php artisan system:backups:cleanup --dry-run -v; normal execution uses the same
policy. --force bypasses only the enabled setting. It never bypasses protection,
minimum counts or active operations. The scheduler invocation adds --scheduled
to record its source. Read-only previews create no execution history or disk
roots; completed runs record one bounded SQL outcome with actual deleted bytes.
A common nonblocking filesystem lease serializes backup creation, upload, restoration, retention changes and deletion. It survives SQL restoration and releases on process exit. Shared backups filesystems must support advisory file locks. Current package migrations are required. Restore maintenance repairs the additive retention/summary schema when an older SQL snapshot predates it.
The Internal API keeps GET/PUT /webadmin/api/system/backup-cleanup with
backups.read / backups.settings.write and destructive POST
/webadmin/api/system/backup-cleanup/run with backups.delete. Original policy
fields remain required; optional lifecycle fields patch named values and omitted
fields are preserved. Null budget_gb removes the budget. GET/PUT
/webadmin/api/system/backups/{backup}/retention read/set protection and expiration
using the same separate read/settings-write capabilities. See discovery for
required fields and valid modes. Transfer to remote storage remains a later phase.
General maintenance cleanup
The Cleanup workspace shows physical file counts and bytes for system backups, export/import packages, public uploads excluding generated variants, generated variants, known temporary workspaces, asset revision files, site public files and standard application logs. Measurements are timestamped and distinguish missing directories, partial scans and unavailable storage. They do not measure hosting quota or allocated database tables. Custom log locations are excluded; shared disk roots may make categories overlap, so do not add them into an installation-wide quota figure. Existing per-site revision sizes describe logical snapshot payloads separately. An unavailable cleanup preview is shown explicitly.
The five maintenance categories are asset-revisions, media-variants,
temporary-workspaces, page-revisions and shared-slot-revisions. Asset
revisions default to at least 90 days and the latest 20 per asset. Temporary
workspaces default to 24 hours. Page and Shared Slot revisions default to at
least 90 days and the latest 50 per owner, with active revision-restore sources
protected. Original Media Library files and current variant fingerprints are
never eligible for variant cleanup. Transfer archives and logs remain operator
managed.
Revision age and minimum-count protection apply together. For example, a page with 20 revisions keeps all 20 even if they are older than 90 days, because the latest 50 are protected. Explicit saved policies, including 365-day retention, are preserved; only the fallback for an unset setting changes to 90 days. Saving settings updates eligibility, and the next enabled scheduled job can apply it.
From CMS 1.105.25, Retention Policy groups category schedules, backup retention and revision/workspace retention in responsive tables. Retention means an age threshold plus the protected minimum count, not a maximum backup count. Manual-backup inclusion and the storage budget remain separate controls; a budget never overrides protection. Existing saved values are preserved on upgrade and after validation errors.
The Scheduler setup and verification link opens an authorized, read-only screen with installation-specific commands and recorded heartbeat evidence. It supports standard hosting cron without Herne, plus optional confirmed CLI setup for the application account. See scheduler setup. Adding a cron entry is not proof of execution. Refresh after 1–2 minutes to check actual activity; cleanup still runs at the schedule shown in the table.
The workspace uses native tabs for Storage and history, Retention and automation, and Cleanup actions. Settings use named WebBlocks UI groups with paired age/count fields, without HTML fieldset/legend frames. Validation/action redirects retain the relevant panel. Retained History's gear actions open the settings tab at the relevant revision policy, and its eye action opens transfer-package management. All use the panel's standard icon actions with accessible labels. Empty categories show a neutral “Nothing to clean” state; categories with eligible items retain the existing destructive confirmation modal. The automatic selection shows each category's own scheduled time.
Run current fresh/package-update migrations before using the new execution summaries. Each category records only its latest manual/scheduled outcome, with start/finish and last-success times, counts, bytes and failures. Partial deletion credits only successfully removed bytes. Missing summary schema blocks cleanup; a stale in-flight summary is shown as interrupted. Reading reports and dry runs do not create execution or scheduler evidence.
Enable scheduled categories individually in Maintenance → Cleanup. New flags are off by default. Daily schedules run asset revisions at 04:00, obsolete media variants at 04:10 and temporary workspaces at 04:20. Page and Shared Slot revision cleanup runs on Sundays at 04:30 and 04:45. Times follow the application's scheduling timezone. The host must run Laravel Scheduler and use a shared cache with atomic locks when multiple workers operate. Existing asset-write revision pruning still applies after asset changes even when its additional daily schedule is off. Existing backup cleanup has its separate policy and schedule.
Temporary cleanup covers recognized timestamp/UUID update runs and abandoned
backup/restore scratch runs under storage/app/temp/system-backups and
storage/app/temp/system-backup-restores. Every nested entry must be older than
the retention threshold. The default remains 24 hours; lowering it is not needed
to recover abandoned work. The installation identity, unknown files or run names,
updater recovery-backup folders, symlinked trees and storage overlapping backup
archives or public uploads are retained. Physical usage includes retained files,
so measured storage can be nonzero with no eligible candidate.
Stale running/pending update history by itself no longer blocks cleanup.
Recent admissions, fresh heartbeats, queued operator jobs and actual held update
locks still protect work. Backup/restore process leases also protect operations
whose database snapshot already says completed. Deletion holds both operation
leases and rechecks age and tree identity; it does not rewrite update history.
If the lease heartbeat cannot be written and read back, cleanup stops safely.
Catalog downloads use file leases while installing; only expired, unlocked
catalog-plugin- temporary files are eligible. Other files in that directory
are not selected. Automatic temporary cleanup remains an explicit per-install
opt-in in Retention and automation; enabling it does not change the age limit
or the other category flags.
php artisan system:cleanup
php artisan system:cleanup media-variants --dry-run
php artisan system:cleanup media-variants
php artisan system:cleanup media-variants --scheduled
The last form runs only if automatic maintenance is enabled for that category. Use any of the five category names for the category argument. A dry run never removes files or writes an execution summary; commands return failure when cleanup or its preview cannot complete.
The Internal API exposes GET and PUT /webadmin/api/system/cleanup through
maintenance.read and maintenance.settings.write, plus destructive
POST /webadmin/api/system/cleanup/{category}/run through maintenance.delete.
Existing retention fields remain required on settings writes. The optional
automatic object patches named boolean category flags; omitted flags keep
their values. The read response adds physical measurements and latest outcomes
under overview.storage_report and overview.automation. Unknown flags and
non-boolean values are rejected. A failed run returns ok: false and a failure
count rather than claiming successful cleanup.
Update workflow and release publishing
Update availability is based on the latest published release version being newer than the running CMS code version. Historical failed update runs and stale stored installed-version values remain inspectable through the run details modal and retained-run CLI, but they do not make a current install actionable by themselves.
The screen is a single status card. When a compatible update is available, it shows the current-to-latest version path, a folded What's new changelog area, the one-click Update to X action with a backup note, and a server-backup advisory linking to the Backups screen so operators take a fresh full backup before a major update. When no update is available, it shows a quiet up-to-date message with installed version and last-checked time.
Super admins have an async update indicator shell in the admin topbar. It is hidden by default, checks update state asynchronously, and becomes visible only when a newer trusted release is available.
Preflight readiness is an explicit pass/fail check list on the screen: database connection, ZIP and sodium PHP extensions, PHP/Composer/process execution, application-root and update-workspace write access, and free disk space. The update action is available only when every check passes and no other update run holds the lock.
Update History renders retained runs as a closed accordion; each run opens its log in a modal, and row deletion is not part of the admin UI. Update run records are pruned automatically, retaining the latest five runs by default. The latest failed run is preserved until a newer successful run exists. Operators with terminal access can inspect retained records with php artisan webblocks:updates:runs, php artisan webblocks:updates:runs --last, or php artisan webblocks:updates:runs --failed, and can run controlled pruning with php artisan webblocks:updates:prune-runs --keep=5.
The update client supports structured metadata from the update service, including title, summary, highlights, fixes, compatibility notes, migration notes, asset notes, operator notes, and technical notes, plus cumulative per-version changelog entries for the What's new accordion. Native CMS release preparation derives summary and highlights from the matching CHANGELOG.md release section and adds release-specific technical metadata, so the Release card should describe the actual published version instead of repeated publishing workflow placeholders. These values are rendered as escaped plain text. Older releases that only provide release_notes still show those notes cleanly, and releases without notes show No release notes were provided for this release.
Shipped catalog synchronization runs automatically as a best-effort step of the update apply chain (webblocks:catalog-repair --all); a sync failure does not fail the update run and can be repaired manually afterward.
For manual maintenance or recovery on an existing install, admins and developers can also run:
php artisan webblocks:catalog-repair --dry-run --all
php artisan webblocks:catalog-repair --all
php artisan block-types:sync-core
webblocks:catalog-repair supports --block-types, --plugin-block-types, --slot-types, --page-layouts, --icons, and --all. It reports created, updated, unchanged, and skipped rows, preserves install-specific custom catalog rows, and can be run repeatedly. block-types:sync-core remains as a lower-level compatibility command for the block type catalog.
--plugin-block-types writes a catalog row for every block type declared by an installed plugin, enabled or not, so the block is placeable in the editor; a disabled plugin's blocks are still filtered out of pickers by PluginBlockCatalog. Plugin lifecycle actions (install, enable, disable, setup, update) already run this sync through PluginRuntimeRefresher, so the scope is a repair path rather than the normal one. A re-sync corrects the plugin-owned columns (name, description, source_type, is_system, is_container) and deliberately leaves category, sort_order, and status alone, so operator curation — retabbing a block, reordering it, or setting it to draft to hide it — survives repair.
Published release packages contain the reusable CMS runtime: source, public assets, migrations, views, routes, configuration, language files and installation stubs. Development tests, scripts, repository metadata, local configuration, documentation sources and install-specific project content are excluded from the distribution archive. Canonical documentation is maintained in its independent repository.
CMS release/update publishing is a native/local maintainer workflow:
git tag -a vX.Y.Z -m "webblocks-cms X.Y.Z"
composer release:push
composer release:prepare
composer release:publish-update -- --dry-run
composer release:publish-update
release:push sends the release branch and annotated tag to origin in one Git push, so the pre-push validation suite runs once. It then mirrors the verified refs to backup without rerunning that hook. release:prepare builds the package ZIP, checksum and Publisher payload from committed HEAD; it requires a clean tree, matching source version, the exact annotated v{VERSION} tag and a reviewed inventory with matching documentation provenance.
Run the local checks and verify the release commit's GitHub CI before publishing. release:publish-update -- --dry-run validates the artifact and configuration without uploading. The actual publish command requires GitHub CLI authentication, signs and uploads the artifact to Publisher, verifies stable-channel metadata, and publishes or updates the matching GitHub Release. Publisher remains the installation update source; a GitHub Release alone does not publish the CMS update channel. If a response is interrupted, inspect both publication states before retrying.
Maintainer publishing requires WEBBLOCKS_PUBLISHER_TOKEN and WEBBLOCKS_PUBLISHER_SIGNING_KEY, loaded at execution from the established maintainer configuration. Do not copy these credentials into a CMS installation or documentation. Installed sites use the package-owned release server, product and stable-channel defaults; ordinary installations do not need Publisher server or channel environment settings. The former updates.webblocksui.com bridge is historical and is not an active configuration path.
Installed CMS working copies are update consumers. They may fetch source history when needed, but must not push upstream or publish CMS updates from an installation checkout.
Contact Mail Diagnostics
Contact Form submissions are saved before notification delivery. Email notification status reflects notification behavior only and does not change editorial classification. Sent means Laravel accepted the send through a configured real mail transport without an exception; it does not guarantee inbox delivery. Failed means a real notification attempt threw a sanitized failure. Skipped or Not configured means no real send occurred. Valid submissions score as allowed, quarantined, or spam; quarantine and spam are retained for review with notification suppressed. Invalid signed proof, a filled generated check field, or submission below the hard minimum time follows generic success without storage. The rolling protection summary counts only requests that reached scoring. See Public Submission Protection for signals, thresholds, privacy boundaries, and tuning.
Contact Form notification recipients resolve in this order:
- Contact Form block
recipient_email - Site default contact recipient from
Site -> Edit -> Contact .envCONTACT_RECIPIENT_EMAIL- safe
MAIL_FROM_ADDRESSfallback
Typical Laravel SMTP .env settings are:
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=no-reply@example.com
MAIL_FROM_NAME="Site Name"
CONTACT_RECIPIENT_EMAIL=contact@example.com
After editing .env mail settings on a production or package install, clear cached configuration when needed:
php artisan optimize:clear
Use the secret-free mail diagnostic command when a Contact Message shows notification failure:
php artisan contact:mail-diagnose
php artisan contact:mail-diagnose --block=137
php artisan contact:mail-diagnose --send-test=operator@example.com
The command reports the resolved mailer, host, port, scheme/encryption fields, username, from address, CONTACT_RECIPIENT_EMAIL, config-cache state, and optional Contact Form block/site recipient fallbacks. It never prints MAIL_PASSWORD or token values. The optional send test reports only success or a sanitized failure detail, so operators can distinguish stale config, host/port/encryption mismatch, username/from mismatch, and invalid mailbox credentials without leaking secrets into terminal logs.
Use Contact Messages as the source of truth for stored submissions and notification state. Development mailers such as log, array, and null should not be treated as delivered email in operations reports.
Operational smoke test:
- Publish or preview a page with the native
contact_formblock. - Submit a test message.
- Confirm the message appears in
/webadmin/contact-messages. - Review notification status and safe failure detail.
- Review the 30-day protection totals and correct false positives through status.
- Run
php artisan contact:mail-diagnose --block=IDif recipient resolution is unclear. - Run
php artisan contact:mail-diagnose --send-test=operator@example.comonly for a controlled SMTP send check.
WebBlocks UI Manager Operator Plugin
WebBlocks UI Manager is an internal/operator plugin for product-specific release operations. It is not bundled into normal CMS runtime packages and ordinary CMS installs should not install it. Build its local artifact from the maintenance repo with:
php plugins/webblocks-ui-manager/build-plugin.php
Upload the generated ZIP through System -> Plugins as a super admin, review the installed plugin detail, then explicitly enable it. Uploaded plugins install disabled by default. Disabled or incompatible installs remain inert, and health is not checked while disabled.
If the plugin declares migrations, enablement does not imply setup is complete. The plugin detail screen shows Setup required / Plugin migrations pending when plugin-owned tables are missing. Use the super-admin Run Plugin Migrations action from the plugin detail screen to run only migrations declared by that installed plugin and only from the plugin install path. The action is idempotent, records a setup result in the plugin enabled-state file, and reports secret-free errors. Enabled plugin routes that are visible before setup must render controlled setup guidance instead of raw database errors.
If an install briefly ran v1.32.67 and created webblocks_ui_manager_* tables, this patch does not drop them automatically. Manual plugin uninstall also preserves plugin-owned tables. Leave them in place unless an operator has confirmed the plugin is not needed and performs a separate manual database cleanup with a backup.
When manually installed, enabled, and set up, the plugin adds /webadmin/plugins/webblocks-ui-manager/releases for WebBlocks UI release metadata, dry-run validation, and local static CDN publishing. CMS super_admin users can open enabled plugin routes through the plugin-owned permissions declared in the manifest; non-super-admin roles require explicit plugin permission grants. If webblocks_ui_manager_releases, webblocks_ui_manager_artifacts, or webblocks_ui_manager_publish_runs is missing, the Releases screen shows setup-required guidance and links back to plugin setup instead of querying missing tables. Prepare a release with local WebBlocks UI dist files:
php artisan webblocks-ui-manager:prepare-release v2.7.9 --artifact=/path/to/webblocks-ui.css --artifact=/path/to/webblocks-icons.css --artifact=/path/to/webblocks-ui.js
The command records release metadata, computes SHA-256 checksums, and prepares manifest metadata for the first-party static convention public/cdn/webblocks-ui/{version}/.... The expected dist files are configured by webblocks-plugins.webblocks_ui_manager.expected_dist_files and default to webblocks-ui.css, webblocks-icons.css, and webblocks-ui.js.
Validate the publish plan without writing files:
php artisan webblocks-ui-manager:publish-release v2.7.9 --dry-run
Apply the local publish after dry-run validation passes:
php artisan webblocks-ui-manager:publish-release v2.7.9
The publish workflow validates source paths, version-to-target path matching, expected dist files, stored checksums, manifest consistency, and idempotency before writing. Existing files with matching checksums are skipped. Existing files with different checksums block the publish. The target is local/project-owned by default through WEBBLOCKS_UI_MANAGER_CDN_BASE_PATH=cdn/webblocks-ui; WEBBLOCKS_UI_MANAGER_CDN_BASE_URL is optional display/URL metadata. The workflow does not deploy to an external production server, publish update-server metadata, or change CMS core WebBlocks UI asset URLs.
System -> Plugins reports disabled, enabled, incompatible, missing-files, and error states separately. Disabled plugins show inactive health, not failure. A plugin configured as enabled but incompatible remains inert: no plugin routes, commands, menus, permissions, settings routes, widgets, assets, block declarations, or health reporter behavior become active.
Manual uninstall is available only for manually uploaded plugins after they have been disabled. It removes the storage-owned installed package directory and enabled-state file, never CMS core files, public /cms assets, project files, vendor files, or storage outside the configured plugin root. It does not run destructive migrations or drop plugin-owned database tables.
External production CDN deployment, hosted CDN smoke checks, generic third-party plugin install/update behavior, arbitrary remote installers, Composer package installation, update-server publishing, and marketplace/catalog flows remain intentionally deferred.
Backup / Restore
Backup / Restore is the environment-level recovery tool.
Backups can include:
- database dump
- CMS-managed uploads from
storage/app/public, excluding regenerablemedia/transforms/files in new backups - site public files from
public/site - archive metadata in
manifest.json
Restore behavior is explicit:
- only completed backups with a valid archive can be restored
- archive download, detail, restore eligibility, and delete actions resolve paths through the same
backupsdisk root and block traversal, symlink escapes, and absolute paths outside that root - missing or unreadable archive files are shown as controlled admin feedback instead of raw filesystem exceptions
- restore creates a fresh pre-restore safety backup first
- restore replaces the current database
- restore replaces
storage/app/publicwhen uploads are included in the archive - MySQL/MariaDB restores run the import with temporary foreign-key and unique-check guards, so valid full dumps remain portable even when table creation order differs between environments
- existing installs updated through System Update also run package-owned repair migrations when needed; this keeps schema contracts such as the
pages(id, site_id)parent key aligned with backups that contain site-scopedpage_translationsforeign keys
New backups record excluded_upload_paths: ["media/transforms/"] in their
version-one manifest. Original images, attachments and other uploads remain in
the archive. Generated variants are rebuilt when the renderer needs them after
restore; absent codecs fall back to the original. Rebuilding all variants in
advance is optional; see Media Image Variants.
Older version-one and legacy archives remain restorable, including cached
variants when present. Same-second backups have distinct filenames so the
pre-restore safety archive cannot overwrite its source.
Use Backup / Restore when you need to recover the install environment, not just one page.
Export / Import
Export / Import is the site portability tool.
Use it to move one site's content between installs.
Site transfer packages are stored on the Laravel filesystem disk named site-transfers, which defaults to storage/app/site-transfers. Fresh Composer consumer installs register this disk automatically and webblocks:install prepares the storage directory, so host applications do not need to edit config/filesystems.php unless they want to provide a custom disk.
Site imports require the target install's database-backed block type and slot type catalogs to contain the rows referenced by the package. Fresh Composer installs seed these catalogs during webblocks:install, explicit catalog maintenance can repair shipped rows with webblocks:catalog-repair, and the import runner performs a final idempotent core catalog sync before validation when a package references a missing shipped core row. If a package references an install-specific custom block or slot that is not present on the target, the admin error lists the exact missing identifiers.
The admin workflow now has two related entry points:
Admin -> Sitesincludes a per-siteExportrow action that opens a modal for the selected site, shows the site name and handle, and can include media files before creating the package.Admin -> Maintenance -> Export / Importis the combined operational screen for transfer history and transfer actions. It showsSite ExportsandSite Importstogether, withRun ExportandRun Importactions in the relevant listing card headers.- The import review screen keeps the validated package action directly below the status and manifest summary, then shows package counts in a compact table for quicker scanning.
- Fresh Composer consumer installs render the Export / Import screens from the package view namespace, so they do not require root
resources/views/admin/site-transfers/*Blade files.
Relationship between the tools:
- Sites row
Exportcreates a package for one selected site and returns to the Sites list with a success message. Export / Importmanages package history and the main export or import operations screen.Sites -> Promoteapplies an export or promotion package to an existing target site with dry run, strategy, safety backup, and preserve rules.
Export / Import covers site-scoped content such as:
- site record and locale assignments
- site variables stored in
site_variables - pages and page translations
- page assets stored in
page_assets - slots and blocks
- Shared Slots and Shared Slot block trees
- page-level Page Layout settings such as
defaultanddocs(still stored internally onpublic_shell) - block translations
- navigation items, including optional navigation item icon slugs used by public Sidebar Navigation renderers
- optional media files
- canonical site-level public override files at
public/site/{site_handle}/css/site.cssandpublic/site/{site_handle}/js/site.jswhenInclude media filesis enabled and those files exist
Page Assets travel through site portability in two layers:
page_assetsrows are always included in site export and import payloads.- When
Include media filesis enabled, referenced/site/...public files are also packaged under the export archive and restored back intopublic/site/...on import. - When
Include media filesis disabled, page asset metadata still imports, but the referenced physical files must already exist on the target install. - Missing page asset files are reported during export and skipped rather than crashing the full package build in the current version.
Site-level public override assets are intentionally narrower than Page Assets: only css/site.css and js/site.js under the source site handle are included, and import restores them under the final target site handle. Arbitrary public/site/... trees, CMS core assets, and public/storage are not part of this path.
Shared Slots are exported and imported as first-class site content:
- Shared Slot metadata such as handle, name, slot compatibility, shell compatibility, and active status is included in the package.
- Shared Slots can also carry an optional Page Layout compatibility constraint. The stored field remains
public_shellfor backward compatibility, empty remains generic, and non-empty values still require exact Page Layout handle matches. - Shared Slot block trees, nested order, translations, and media references travel through the same block and media packaging pipeline used for normal pages.
- Page slots that use
shared_slotexport a stable Shared Slot handle reference and are remapped to the target site's imported Shared Slot during import. - Page payloads preserve each page's Page Layout so docs-shell pages keep compatible docs Shared Slot assignments after import.
- Install-level Page Layout definitions and Page Layout Slot definitions themselves are not part of site export/import in V1.
- Custom page
public_shellhandles still transfer with the page payload, so target installs should provide matching Page Layout handles when they rely on custom layouts. - If a matching Page Layout handle does not exist on the target install, public rendering falls back safely.
- Hidden Shared Slot source pages are kept internal and are not treated as ordinary user-facing pages in the package.
- Shared Slot revision history is excluded from export/import, matching the current page revision portability boundary.
Site Variables are also portable site content:
site_variablesrows are exported and imported with the site package.- Variable keys, labels, values, sort order, and enabled state are preserved.
- Public token behavior is not evaluated during export or import; raw values are transferred as stored.
It does not include install-global runtime data such as users, backups, update history, sessions, or contact submissions.
It also does not require the derived public search index as portable content:
public_search_indexis runtime-derived data- export/import payloads do not need search rows to recreate the site
- use
php artisan search:rebuildafter import when you need fresh search rows immediately
Single Page JSON Import
Single Page JSON Import is the page-scoped admin workflow for creating one new page from a JSON file.
- admin path:
Admin -> Pages -> Import Page - scope: one new page in one selected site
- schema:
webblocks.cms.page.v1 - result: always creates a new draft page in V1
- it does not update an existing page in V1
- it does not import source page revision history
- it does not replace the site-level
Export / Importworkflow
What V1 imports:
- page core fields needed for draft creation, including page-level Page Layout
- page translations keyed by locale code for enabled target-site locales
- page slots, including
shared_slotreferences by compatible same-site Shared Slot handle - page-owned block trees with nested parent-child order
- supported block translation rows for translated block families
- page asset metadata for valid local
/site/...CSS and JS paths
V1 constraints:
- target-site translated path conflicts block the import before writes
- Shared Slot references must already exist on the target site and be compatible by site, active state, shell, and slot name
- the importer does not create Shared Slots automatically
- unsupported schema values are rejected explicitly
- the importer is transactional and leaves no partial page when validation fails
See docs/examples/page-import-v1.json for the sample payload.
Site Promotion
Site Promotion is the package-based workflow for promoting site-owned content from a source package into an existing target site.
Use it when:
- the target site already exists
- site-owned content needs controlled promotion into that site
- install-level, environment-specific, and live-runtime data must be preserved
How it differs from other tools:
- Export / Import creates or manages site transfer packages and creates a new local site from a package by default
- Site Promotion applies package content into an existing target site
- Site Clone duplicates site-owned content inside the current install without a package
- Backup / Restore is environment recovery and can replace the current database or uploads
- Updates change CMS product code and installed version, not site-owned content
V1 workflow:
- admin path:
Admin -> Sites -> Promote - commands:
site-promotion:inspect,site-promotion:dry-run,site-promotion:apply - dry run is required before apply
- apply creates a normal safety backup first
- apply rebuilds the target site's derived search rows after success
Preserved areas include:
- users and roles
- sessions, cache, jobs, and queues
- backups and update history
- visitor reports and contact submissions
- live domains and site domain records
- environment configuration, install secrets, and internal tokens
- derived
public_search_indexrows
Supported strategies:
additive_update: create missing source content and update matching target content without removing extra target contentmirror: create and update matching source content, then archive, deactivate, or remove absent target site-owned content where safe in V1
Project Imports
Install-specific migration and website import workflows belong in project/, not CMS core.
Search Index
Search V1 adds an install-level operational screen and command for the derived public search index.
- admin screen:
Admin -> Maintenance -> Search Rebuild - rebuild command:
php artisan search:rebuild
The Search Rebuild screen reviews derived public search index coverage for published pages by site and locale, and can safely rebuild the index when derived rows need to be refreshed.
Supported rebuild scopes:
- whole install
- one site with
--site= - one locale with
--locale= - one page with
--page=
Search rebuild is non-destructive:
- it deletes and recreates only derived rows inside the requested scope
- it does not modify page, block, translation, Shared Slot, or media content
- it does not require destructive database reset commands
System Settings
System Settings is the compact install-level configuration screen. Editable groups are split into separate focused cards with their own save actions; Runtime Information is read-only.
It keeps:
- Project Name and Project Tagline for admin-only install context
- default locale
- timezone, which is the install-wide default and the fallback for any site that does not set its own
- admin listing rows per page for paginated admin listing screens
- CMS Mail mode and custom mail settings for CMS-owned notifications
- cookie or privacy banner settings
- product version information
- environment information
It does not control:
- the fixed WebBlocks CMS admin brand labels
- public site branding
- public site SEO defaults
- public favicon, public search scope, or page translation SEO
- host/root auth mail or site contact form routing
.envfile contents or environment variables
Project Identity helps distinguish one CMS install from another in the admin topbar and browser title. Those public-facing values still live on each Site or page translation instead.
Admin listing rows per page defaults to 15, accepts custom numeric values such as 10 or 12, and changes only the default row count used by paginated admin listing screens. It does not affect public pagination.
CMS Mail defaults to Laravel environment mail configuration. When the mode is changed to CMS custom settings, CMS-owned password reset emails and future CMS-owned system notifications use the database-backed CMS mail settings through a scoped CMS mailer. Custom mail fields are shown only when CMS custom mail mode is selected. Custom CMS mail settings do not overwrite .env; disabling custom CMS mail returns CMS mail to the existing Laravel MAIL_* configuration. Stored mail secrets are never shown in plain text, blank secret updates keep the existing stored value, and diagnostics report sensitive fields only as configured or not configured. Custom SMTP diagnostics now warn when required send settings are incomplete or invalid, and password reset mail send failures return a controlled CMS mail error while logging only secret-safe technical context. The diagnostics table includes a super-admin Send Test Email action that sends a simple CMS system message through the same CMS mail resolver path as CMS-owned password reset mail without including secrets, reset tokens, or raw configuration dumps.
Site Clone
Site Clone duplicates site-owned content from one site into another site inside the same install.
Use Site Clone when:
- you need a second site inside the current install
- you want to duplicate page, slot, block, navigation, and locale structure without creating an export package first
- you want Shared Slots and Shared Slot-backed page slot assignments to move with the cloned site
Site Clone is different from Export / Import:
- Site Clone works inside the current install
- Export / Import is for moving a site package between installs
- Both Site Clone and Export / Import include Shared Slots, Shared Slot block trees, translations, media references, and page-level Page Layout settings, while remapping consuming page slots to target-site Shared Slots instead of leaving cross-site references behind
- Both Site Clone and Export / Import also include site-scoped
site_variables. - Shared Slot revision history is not cloned, matching the current page revision clone boundary.
When To Use Which Tool
Use Revisions When
- one page needs to be restored
- you need editorial recovery inside the current install
Use Backup / Restore When
- the environment needs recovery
- the database or uploads need to be rolled back together
Use Export / Import When
- a site needs to be moved to another install
- you need a portable package for one site
Use Site Clone When
- you need to duplicate one site into another site inside the same install
- you want to keep the work inside the current environment
Use System Updates When
- applying a published CMS release to the current install
Install-Level Vs Site-Level Boundaries
- Updates, backups, restore, and site transfer tools are install-level features
- pages, media, navigation, and editorial workflow are primarily site-scoped content features
- users are install-level accounts, even when some roles are restricted to assigned sites
- release packaging and installed-path preservation are separate concerns: releases do not ship
project/, while installedproject/content is preserved across updates
Visitor report trends and retention
Visitor Reports now derives a time chart, an equal-length previous-period comparison, measurement coverage and page detail modals from the existing records. It does not add public scripts, cookies, identifiers, events or third-party transfers. Existing consent decisions continue to govern identifier collection.
- Charts fill missing days with zero recorded views. Ranges longer than 90 days use equally sized multi-day buckets, labelled in the chart and accessible value table, opened with the chart-values button in a WebBlocks UI modal. Dates use the same application/database calendar as existing reports.
- The previous period ends immediately before the selected period. A zero previous total has no percentage change. Periods including today or future dates are labelled provisional. Counts describe retained records, not proof that tracking was continuously active or that an empty historical period had no visitors.
- Coverage is the share of views with a usable consent-based identifier in retained detail records. It is not a consent acceptance rate. Visitor/session estimates describe this subset and are not verified counts of people.
- Tracking status shows the last detailed record within the accessible selected site scope, independently of date, locale and traffic filters. It does not claim that the scheduler is running or that absent records prove a tracking failure.
- Content rows open WebBlocks UI modals with page/site/locale totals and independent device/referrer breakdowns. A whole breakdown is hidden if any group has fewer than five views, avoiding direct subtraction of a suppressed cell. This is a display safeguard, not a guarantee of anonymisation. No individual journeys are added. The ten largest groups are shown when a breakdown is available.
The report follows the labelled WebBlocks UI filter-bar contract, with summary metrics above the chart and detailed report tabs below. Measurement coverage, tracking status, privacy context and retention policy are available in the Report information modal. Missing migrations, disabled cleanup and archived record notices remain visible on the report itself.
The chart uses the bundled WebBlocks UI 2.26.0 wb-chart component and the
modal's visitor-chart-data table as its source. Numeric attributes contain
unformatted counts; visible cells retain localized formatting. UI owns SVG
drawing, axes, resize and keyboard interaction; CMS keeps queries, permissions,
privacy and aggregation. No external chart library or additional visitor
collection is introduced.
Cleanup policy
The package registers visitors:cleanup daily at 03:45 through Laravel Scheduler.
The host must run its normal scheduler; no public request performs cleanup.
The scheduled command and manual command share an atomic cache lock. Use a shared
lock-capable cache when scheduling on multiple servers.
| Configuration | Default | Meaning |
|---|---|---|
| CMS_VISITOR_CLEANUP_ENABLED | true | Enable scheduled/manual visitor cleanup. |
| CMS_VISITOR_DETAIL_RETENTION_DAYS | 90 | Roll up records before the start of the day this many days ago. |
| CMS_VISITOR_TOTAL_RETENTION_DAYS | 400 | Delete totals before the start of the day this many days ago; never shorter than detail retention. |
These are product defaults, not legal retention requirements or a legal compliance certification. Site owners can choose shorter periods for their purposes. Existing installs with a working scheduler start applying this policy after the new migration and code are installed. There is no deletion inside the migration itself.
Preview and execute locally on the host:
php artisan visitors:cleanup --dry-run
php artisan visitors:cleanup
Cleanup runs even when new visit collection is disabled. Disabling collection must not leave historical identifiers stored indefinitely. Disabling cleanup itself is shown on the report. Missing summary migrations block cleanup without deleting records and leave existing reports operational.
Expired records are replaced transactionally, one day at a time, with rows in
wbcms_visitor_daily_totals: site, locale (zero for unknown), day, total views and
bot views. They retain no page path, referrer, campaign, device, exact visit time,
IP hash or session identifier. Late records merge into existing totals once.
Records older than the total retention horizon, records without a date, and expired
records whose site has been deleted are discarded without a rollup. Deleting a site
also removes its daily totals. Existing backups have their own retention lifecycle;
this command does not rewrite backup archives.
The main totals, chart and comparison combine retained detail with daily totals and preserve site authorization plus site/locale/traffic filters. Bot totals include both sources. Detailed tables and session metrics use only remaining detail records; an explicit notice explains the difference when a period includes rollups. Daily unique visitors or sessions are deliberately not summed across days. The report never reconstructs visitor histories from the retained summaries.
Fresh installs and existing-install update migrations both create the totals table. Run the normal package migration/update flow before enabling scheduled cleanup. New insight strings ship in English and Turkish; other admin locales use the normal English fallback where an insight translation is unavailable.