feat: admin settings page UI/UX pass with WordPress-native patterns
- Header: h1 + logo in a wrapping flex row, hr.wp-header-end before the tabs, aria-current on the active tab; own CSS/JS enqueued only on this screen. - Verbindung: ordered "So verbindest du Claude" steps, copy button (core clipboard.js + wp.a11y.speak), Site-Health-style status list, German details. - Zugriff: roles as form-table fieldset, connections with site date format, empty-state row, confirm on revoke; ConnectionUsage records last use via WP_Application_Passwords::record_application_password_usage (once a day). - Tools: plain-language legend, counter as inline notice, groups as core postboxes (postbox.js toggles, per-user closed state, summary when closed), fixed column widths with new "Art" column, submit above and below. - Protokoll: site-timezone times with UTC tooltip, result icons, filters with labels in the list table nav, retention link to Limits. - Limits: short labels with units and descriptions; snapshot count hidden but preserved. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,13 +18,18 @@ final class LogListTable extends \WP_List_Table
|
||||
{
|
||||
private const PER_PAGE = 50;
|
||||
|
||||
/** @var (callable(): void)|null */
|
||||
private $renderFilters;
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* ability: string, user_id: int, result?: string, date_from?: string, date_to?: string
|
||||
* } $filters
|
||||
* @param (callable(): void)|null $renderFilters prints the filter controls into the top table nav
|
||||
*/
|
||||
public function __construct(private ChangeLog $log, private array $filters)
|
||||
public function __construct(private ChangeLog $log, private array $filters, ?callable $renderFilters = null)
|
||||
{
|
||||
$this->renderFilters = $renderFilters;
|
||||
parent::__construct(['singular' => 'lit_mcp_log_entry', 'plural' => 'lit_mcp_log_entries', 'ajax' => false]);
|
||||
}
|
||||
|
||||
@@ -32,7 +37,7 @@ final class LogListTable extends \WP_List_Table
|
||||
public function get_columns(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => __('Zeit (UTC)', 'wp-lit-mcp'),
|
||||
'created_at' => __('Zeit', 'wp-lit-mcp'),
|
||||
'user' => __('Benutzer', 'wp-lit-mcp'),
|
||||
'client' => __('Client', 'wp-lit-mcp'),
|
||||
'ability' => __('Tool', 'wp-lit-mcp'),
|
||||
@@ -57,6 +62,22 @@ final class LogListTable extends \WP_List_Table
|
||||
esc_html_e('Keine Einträge.', 'wp-lit-mcp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters left of the pagination in the top table nav, as on WordPress' own list screens.
|
||||
*
|
||||
* @param string $which
|
||||
*/
|
||||
protected function extra_tablenav($which): void
|
||||
{
|
||||
if ($which !== 'top' || $this->renderFilters === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="alignleft actions">';
|
||||
($this->renderFilters)();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights rows with result "partial" (a write that failed after an irreversible
|
||||
* step, spec §2 principle 7/8.2) so admins notice them without reading every row.
|
||||
@@ -65,12 +86,61 @@ final class LogListTable extends \WP_List_Table
|
||||
*/
|
||||
public function single_row($item): void
|
||||
{
|
||||
$style = ($item['result'] ?? '') === 'partial' ? ' style="background:#fcf3e3;"' : '';
|
||||
printf('<tr%s>', $style); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed string.
|
||||
$class = ($item['result'] ?? '') === 'partial' ? ' class="lit-mcp-partial"' : '';
|
||||
printf('<tr%s>', $class); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed string.
|
||||
$this->single_row_columns($item);
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Stored in UTC (MySQL format); shown in the site's timezone and date/time format,
|
||||
* with the UTC value as tooltip.
|
||||
*
|
||||
* @param array<string, mixed> $item
|
||||
*/
|
||||
protected function column_created_at($item): string
|
||||
{
|
||||
$utc = (string) ($item['created_at'] ?? '');
|
||||
$timestamp = preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $utc) === 1
|
||||
? strtotime($utc . ' UTC')
|
||||
: false;
|
||||
if ($timestamp === false) {
|
||||
return '–';
|
||||
}
|
||||
|
||||
$format = get_option('date_format') . ' ' . get_option('time_format');
|
||||
|
||||
return sprintf(
|
||||
'<span title="%s">%s</span>',
|
||||
esc_attr('UTC: ' . $utc),
|
||||
esc_html((string) wp_date($format, $timestamp))
|
||||
);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $item */
|
||||
protected function column_ability($item): string
|
||||
{
|
||||
return '<span class="lit-mcp-tool-name">' . esc_html((string) ($item['ability'] ?? '')) . '</span>';
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $item */
|
||||
protected function column_result($item): string
|
||||
{
|
||||
$result = (string) ($item['result'] ?? '');
|
||||
[$icon, $word] = match ($result) {
|
||||
'ok' => ['yes-alt', __('erfolgreich', 'wp-lit-mcp')],
|
||||
'partial' => ['warning', __('teilweise', 'wp-lit-mcp')],
|
||||
default => ['dismiss', $result],
|
||||
};
|
||||
|
||||
return sprintf(
|
||||
'<span class="lit-mcp-result lit-mcp-result-%1$s">'
|
||||
. '<span class="dashicons dashicons-%1$s" aria-hidden="true"></span> %2$s</span>',
|
||||
esc_attr($icon),
|
||||
esc_html($word)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $item
|
||||
* @param string $column_name
|
||||
@@ -144,7 +214,7 @@ final class LogListTable extends \WP_List_Table
|
||||
return sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url(admin_url('revision.php?revision=' . $revisionId)),
|
||||
esc_html__('ansehen / wiederherstellen', 'wp-lit-mcp')
|
||||
esc_html__('Revision ansehen', 'wp-lit-mcp')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user