count_open();
Menu::header( __( 'Aftercare — Dashboard', 'aftercare' ) );
if ( isset( $_GET['aftercare_ran'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
echo '
' . esc_html__( 'Daily checks ran. Vitals, breach detection and retention are up to date.', 'aftercare' ) . '
';
}
if ( '' === (string) Options::get( 'api_key' ) ) {
echo '';
printf(
/* translators: %s: settings page URL */
wp_kses_post( __( 'Add your Google API key in Aftercare Settings to start pulling Core Web Vitals from the Chrome UX Report. First data appears within minutes.', 'aftercare' ) ),
esc_url( admin_url( 'admin.php?page=aftercare-settings' ) )
);
echo '
';
}
echo '';
printf(
/* translators: %s: URL being monitored */
esc_html__( 'Monitoring %s (p75, field data)', 'aftercare' ),
esc_html( $primary_url )
);
echo '
';
echo '';
foreach ( Options::METRICS as $metric ) {
$this->render_metric_card( $samples->series( $primary_url, $metric, 30 ), $primary_url, $metric );
}
echo '
';
if ( LabClient::enabled() ) {
$this->render_lab_section( $samples, $primary_url );
}
echo '';
// Open incidents summary.
echo '
';
echo '
' . esc_html__( 'Incidents', 'aftercare' ) . '
';
if ( $open_count > 0 ) {
echo '
';
printf(
/* translators: %d: number of open incidents */
esc_html( _n( '%d open incident needs attention.', '%d open incidents need attention.', $open_count, 'aftercare' ) ),
(int) $open_count
);
echo '
';
} else {
echo '
' . esc_html__( 'No open incidents. All metrics within budget.', 'aftercare' ) . '
';
}
echo '
' . esc_html__( 'View incidents', 'aftercare' ) . '
';
echo '
' . esc_html__( 'Maintenance', 'aftercare' ) . '
';
echo '
';
echo '
';
// Recent ledger events.
echo '
';
echo '
' . esc_html__( 'Recent changes', 'aftercare' ) . '
';
$events = $ledger->query( array(), 10 );
if ( empty( $events ) ) {
echo '
' . esc_html__( 'No changes recorded yet. Plugin updates, theme switches, publishes and settings changes will appear here within seconds of happening.', 'aftercare' ) . '
';
} else {
echo '
';
foreach ( $events as $event ) {
LedgerPage::render_event_row( $event );
}
echo '
';
echo '
' . esc_html__( 'Full ledger', 'aftercare' ) . '
';
}
echo '
';
echo '
';
Menu::footer();
}
/**
* Lab cards sit under their own heading and are never merged into the
* field cards above: a Lighthouse run is a synthetic load, not a visitor.
*/
private function render_lab_section( SampleRepository $samples, string $url ): void {
echo '' . esc_html__( 'Lab checks', 'aftercare' ) . '
';
echo '' . esc_html__( 'Lighthouse, mobile profile, median of 3 runs per day through the PageSpeed Insights API. Useful when field data is thin; not a measure of real visitors.', 'aftercare' ) . '
';
$error = LabClient::last_error();
if ( $error ) {
echo '';
printf(
/* translators: 1: URL, 2: error message from the PageSpeed Insights API */
esc_html__( 'The last lab check of %1$s failed: %2$s', 'aftercare' ),
esc_html( $error['url'] ),
esc_html( $error['message'] )
);
echo '
';
}
echo '';
foreach ( Options::LAB_METRICS as $metric ) {
$this->render_metric_card( $samples->lab_series( $url, $metric, 30 ), $url, $metric );
}
echo '
';
}
/**
* @param array $series
*/
private function render_metric_card( array $series, string $url, string $metric ): void {
$latest = ! empty( $series ) ? end( $series )['value'] : null;
$budget = Options::budget( $metric, $url );
$status = \Aftercare\Vitals\Status::evaluate( $latest, $budget );
$labels = array(
'pass' => __( 'Pass', 'aftercare' ),
'warn' => __( 'Warn', 'aftercare' ),
'fail' => __( 'Fail', 'aftercare' ),
'empty' => __( 'No data', 'aftercare' ),
);
echo '';
echo '
';
echo '' . esc_html( $metric ) . '';
echo '' . esc_html( $labels[ $status ] ) . '';
echo '
';
echo '
' . ( null !== $latest ? esc_html( Util::format_metric( $metric, $latest ) ) : '—' ) . '
';
echo '
';
printf(
/* translators: %s: budget value */
esc_html__( 'Budget %s', 'aftercare' ),
esc_html( Util::format_metric( $metric, $budget ) )
);
echo '
';
echo '
';
echo '
';
}
}