$row A log row. * @param array $names aafm_activity_connection_names() for the page. * @return array{id:int,time:string,principal:string,connection:string,ability:string,detail:string,detail_link:?array,status:string,variant:string,duration:string,ip:string,arg_keys:string} */ function aafm_activity_row_view( array $row, array $names ): array { $variants = array( 'success' => 'success', 'error' => 'danger', 'denied' => 'warn', 'started' => 'neutral', ); $status = (string) ( $row['status'] ?? '' ); $ability = (string) ( $row['ability'] ?? '' ); $detail_raw = aafm_localize_denied_detail( (string) ( $row['detail'] ?? '' ) ); $ip = (string) ( $row['source_ip'] ?? '' ); $duration = isset( $row['duration_ms'] ) ? (int) $row['duration_ms'] : null; return array( 'id' => (int) ( $row['id'] ?? 0 ), 'time' => (string) ( $row['created_at'] ?? '' ), 'principal' => (string) ( $row['principal_login'] ?? '' ) . ' (#' . (int) ( $row['principal_user_id'] ?? 0 ) . ')', 'connection' => aafm_activity_connection_label( $row, $names ), 'ability' => $ability, 'detail' => $detail_raw, 'detail_link' => aafm_activity_detail_link( $ability, $detail_raw ), 'status' => $status, 'variant' => $variants[ $status ] ?? 'neutral', 'duration' => aafm_format_duration( $duration ), 'ip' => '' !== $ip ? $ip : '-', 'arg_keys' => (string) ( $row['arg_keys'] ?? '' ), ); } /** * The summary as plain values for the card and the AJAX payload. * * @param array{ok:bool,since:string,total:int,success:int,error:int,denied:int,success_rate:?float,top_tools:list} $summary Result of aafm_activity_summary(). * @return array{ok:bool,total:int,success:int,error:int,denied:int,rate:string,top:list} */ function aafm_activity_summary_view( array $summary ): array { $rate = isset( $summary['success_rate'] ) ? number_format_i18n( (float) $summary['success_rate'], 1 ) . '%' : '-'; return array( 'ok' => $summary['ok'], 'total' => $summary['total'], 'success' => $summary['success'], 'error' => $summary['error'], 'denied' => $summary['denied'], 'rate' => $rate, 'top' => $summary['top_tools'], ); } /** * Render the 7-day summary card. admin.js fills the same [data-summary] nodes on Live refresh. * * @param array{ok:bool,since:string,total:int,success:int,error:int,denied:int,success_rate:?float,top_tools:list} $summary Result of aafm_activity_summary(). * @return void */ function aafm_render_activity_summary( array $summary ): void { $v = aafm_activity_summary_view( $summary ); // A failed summary query is not "zero calls": show a hyphen and say so. $num = static function ( int $n ) use ( $v ): string { return $v['ok'] ? number_format_i18n( $n ) : '-'; }; // The Dashboard's stat cards, one per figure, so both tabs show numbers the same way. echo '
'; echo '
'; $stats = array( 'total' => array( __( 'Calls, last 7 days', 'agent-abilities-for-mcp' ), $num( $v['total'] ), 'bolt' ), 'rate' => array( __( 'Success rate', 'agent-abilities-for-mcp' ), $v['ok'] ? $v['rate'] : '-', 'success' ), 'denied' => array( __( 'Denied', 'agent-abilities-for-mcp' ), $num( $v['denied'] ), 'lock' ), 'errors' => array( __( 'Errors', 'agent-abilities-for-mcp' ), $num( $v['error'] ), 'warning' ), ); foreach ( $stats as $key => $stat ) { aafm_render_stat_head( 'aafm-log-stat', $stat[0], $stat[2] ); echo '
' . esc_html( $stat[1] ) . '
'; } aafm_render_stat_head( 'aafm-log-top', __( 'Top tools', 'agent-abilities-for-mcp' ), 'audit' ); echo '
    '; if ( ! $v['ok'] ) { echo '
  1. ' . esc_html__( 'Summary unavailable.', 'agent-abilities-for-mcp' ) . '
  2. '; } elseif ( array() === $v['top'] ) { echo '
  3. ' . esc_html__( 'No calls in the last 7 days.', 'agent-abilities-for-mcp' ) . '
  4. '; } foreach ( $v['ok'] ? $v['top'] : array() as $tool ) { echo '
  5. ' . esc_html( (string) $tool['ability'] ) . ' ' . esc_html( number_format_i18n( (int) $tool['count'] ) ) . '
  6. '; } echo '
'; echo ''; // .aafm-log-stats echo '

' . esc_html__( 'Agent tool calls only, updated about once a minute. Times are UTC.', 'agent-abilities-for-mcp' ) . '

'; echo '
'; } /** * Render the filter row. Every control carries data-filter-key, which admin.js reads; nothing here * submits a form. Values come from the already-normalized filters and are escaped. * * @param array $filters Normalized filters (from the URL on first render). * @return void */ function aafm_render_activity_filters( array $filters ): void { $field = static function ( string $label, string $control ): void { echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $control is built from esc_attr()/esc_html() below. }; $text = static function ( string $key, string $placeholder, string $type = 'text' ) use ( $filters ): string { return sprintf( '', esc_attr( $type ), esc_attr( $key ), esc_attr( isset( $filters[ $key ] ) ? (string) $filters[ $key ] : '' ), esc_attr( $placeholder ) ); }; echo ''; }