diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..5007c14 --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,340 @@ +/* Einstellungen → LIT MCP. Only small additions to WordPress' own admin styles. */ + +/* Header: title left, logo right; wraps instead of overlapping on narrow screens. */ +.lit-mcp-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px 16px; +} + +.lit-mcp-logo { + display: block; + height: 30px; + width: auto; +} + +@media screen and (max-width: 782px) { + .lit-mcp-logo { + height: 22px; + } +} + +.lit-mcp .nav-tab-wrapper { + margin-bottom: 1em; +} + +/* Muted secondary text (tool names, client URLs). */ +.lit-mcp-muted, +.lit-mcp-tool-name { + color: #646970; + font-size: 12px; + overflow-wrap: anywhere; +} + +.lit-mcp-tool-name { + font-family: Consolas, Monaco, monospace; +} + +/* Tab "Verbindung". */ +.lit-mcp-steps { + max-width: 50rem; +} + +.lit-mcp-steps li { + margin-bottom: 0.75em; +} + +.lit-mcp-copy-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px 8px; + margin-top: 6px; +} + +.lit-mcp-copy-row input.large-text { + flex: 1 1 22rem; + width: auto; +} + +.lit-mcp-copy-row .success { + color: #007017; +} + +.lit-mcp-status { + margin: 0 0 1em; +} + +.lit-mcp-status li { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 0 6px; + margin-bottom: 8px; +} + +.lit-mcp-status .dashicons { + align-self: center; +} + +.lit-mcp-status-detail { + color: #50575e; +} + +.lit-mcp-status-yes-alt .dashicons, +.lit-mcp-result-yes-alt .dashicons { + color: #00a32a; +} + +.lit-mcp-status-warning .dashicons, +.lit-mcp-result-warning .dashicons { + color: #dba617; +} + +.lit-mcp-status-dismiss .dashicons, +.lit-mcp-result-dismiss .dashicons { + color: #d63638; +} + +/* Tab "Zugriff". */ +.lit-mcp-connections + .description { + margin-top: 8px; +} + +.lit-mcp-connections .column-actions { + width: 8em; + text-align: right; +} + +@media screen and (max-width: 782px) { + .lit-mcp-connections .column-created { + display: none; + } + + .lit-mcp-connections .column-actions { + width: auto; + } + + .lit-mcp-copy-row input.large-text { + font-size: 13px; + } +} + +/* Tab "Tools": legend, collapsible groups (WordPress postboxes) with aligned columns. */ +.lit-mcp-legend { + display: grid; + grid-template-columns: max-content 1fr; + gap: 2px 12px; + margin: 1em 0; +} + +.lit-mcp-legend dt { + font-weight: 600; +} + +.lit-mcp-legend dd { + margin: 0; +} + +.lit-mcp-groups .postbox-header { + flex-wrap: wrap; + gap: 0 12px; + padding-left: 12px; +} + +.lit-mcp-groups .postbox .hndle { + cursor: pointer; + justify-content: flex-start; + gap: 6px; + padding: 8px 0; + margin: 0; + font-size: 14px; + line-height: 1.4; +} + +.lit-mcp-groups .hndle .count { + color: #646970; + font-weight: 400; +} + +.lit-mcp-group-summary { + color: #646970; + font-size: 13px; + font-weight: 400; +} + +.lit-mcp-group-summary::before { + content: "– "; +} + +.lit-mcp-groups .postbox:not(.closed) .lit-mcp-group-summary { + display: none; +} + +.lit-mcp-group-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 16px; + padding: 6px 0; +} + +.lit-mcp-group-controls .description { + font-style: italic; +} + +.lit-mcp-groups .handle-actions { + padding-right: 6px; +} + +/* Same arrow indicator as WordPress' own meta boxes (core scopes it to .meta-box-sortables). */ +.lit-mcp-groups .postbox .toggle-indicator::before { + content: "\f142"; + content: "\f142" / ''; + display: inline-block; + font: normal 20px/1 dashicons; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: none; +} + +.lit-mcp-groups .postbox.closed .toggle-indicator::before { + content: "\f140"; + content: "\f140" / ''; +} + +.lit-mcp-groups .postbox .inside { + margin: 0; + padding: 0; +} + +.lit-mcp-tools-table { + table-layout: fixed; + border: 0; + box-shadow: none; +} + +.lit-mcp-tools-table .column-tool { + width: 55%; +} + +.lit-mcp-tools-table .column-kind { + width: 15%; +} + +.lit-mcp-tools-table .column-mode { + width: 30%; +} + +.lit-mcp-tools-table td { + vertical-align: middle; +} + +.lit-mcp-tools-table select { + max-width: 100%; +} + +.lit-mcp-tools-table .description { + margin: 4px 0 0; +} + +.lit-mcp-kind { + color: #50575e; + white-space: nowrap; +} + +.lit-mcp-kind .dashicons { + font-size: 16px; + width: 16px; + height: 16px; + vertical-align: text-bottom; + color: #787c82; +} + +@media screen and (max-width: 782px) { + .lit-mcp-group-controls { + order: 3; + flex-basis: 100%; + padding-bottom: 10px; + } + + .lit-mcp-tools-table .column-tool { + width: 45%; + } + + .lit-mcp-tools-table .column-kind { + width: 12%; + } + + .lit-mcp-tools-table .column-mode { + width: 43%; + } + + .lit-mcp-tools-table th, + .lit-mcp-tools-table td { + padding-left: 6px; + padding-right: 6px; + } + + /* Icon only on phones; the word stays for screen readers (and as tooltip-free fallback). */ + .lit-mcp-kind-text { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } + + .lit-mcp-tools-table select { + width: 100%; + font-size: 14px; + padding-right: 20px; + background-position: right 2px top 55%; + } +} + +/* Tab "Protokoll". */ +.lit-mcp .wp-list-table .column-created_at { + width: 10em; +} + +.lit-mcp .wp-list-table .column-user, +.lit-mcp .wp-list-table .column-client { + width: 8em; +} + +.lit-mcp .wp-list-table .column-ability, +.lit-mcp .wp-list-table .column-object { + width: 11em; +} + +.lit-mcp .wp-list-table .column-revision, +.lit-mcp .wp-list-table .column-result { + width: 8em; +} + +.lit-mcp .wp-list-table .lit-mcp-partial { + background: #fcf9e8; +} + +.lit-mcp-result .dashicons { + font-size: 18px; + width: 18px; + height: 18px; + vertical-align: text-bottom; +} + +.lit-mcp .tablenav .actions select, +.lit-mcp .tablenav .actions input[type="date"] { + float: left; + margin-right: 6px; + max-width: 12.5rem; +} + +/* Tab "Limits". */ +.lit-mcp-unit { + margin-left: 4px; +} diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..e950819 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,45 @@ +/* Einstellungen → LIT MCP: copy the server URL, collapsible tool groups, filter paging. */ +( function () { + 'use strict'; + + document.addEventListener( 'DOMContentLoaded', function () { + var hideTimeout; + + // "URL kopieren" – like WordPress' "Copy URL to clipboard" in the media library. + if ( window.ClipboardJS && document.querySelector( '.lit-mcp-copy' ) ) { + new window.ClipboardJS( '.lit-mcp-copy' ).on( 'success', function ( event ) { + var success = event.trigger.parentNode.querySelector( '.success' ); + + event.clearSelection(); + event.trigger.focus(); + if ( success ) { + clearTimeout( hideTimeout ); + success.classList.remove( 'hidden' ); + hideTimeout = setTimeout( function () { + success.classList.add( 'hidden' ); + }, 3000 ); + } + if ( window.wp && window.wp.a11y && window.litMcpAdmin ) { + window.wp.a11y.speak( window.litMcpAdmin.copied ); + } + } ); + } + + // Tool groups: WordPress' own postbox toggles, closed state saved per user. + if ( window.postboxes && window.pagenow && document.querySelector( '.lit-mcp-groups' ) ) { + window.postboxes.add_postbox_toggles( window.pagenow ); + } + + // Protokoll: a changed filter starts again on page 1. + document.querySelectorAll( '.lit-mcp .tablenav .actions select, .lit-mcp .tablenav .actions input' ).forEach( + function ( control ) { + control.addEventListener( 'change', function () { + var paged = control.form && control.form.querySelector( 'input[name="paged"]' ); + if ( paged ) { + paged.value = '1'; + } + } ); + } + ); + } ); +}() ); diff --git a/src/Admin/AccessTab.php b/src/Admin/AccessTab.php index c183f59..5ddd9be 100644 --- a/src/Admin/AccessTab.php +++ b/src/Admin/AccessTab.php @@ -89,16 +89,17 @@ final class AccessTab implements AdminTab public function render(): void { $allowed = $this->access->allowedRoles(); + $legend = __('Rollen mit Zugriff', 'wp-lit-mcp'); - echo '
' . esc_html__( - 'Nur Benutzer dieser Rollen können Tools nutzen; zusätzlich gelten ihre normalen WordPress-Rechte.', - 'wp-lit-mcp' - ) . '
'; echo ''; @@ -160,31 +165,42 @@ final class AccessTab implements AdminTab private function renderConnections(): void { - echo '| %s | %s | ' + . '%s | %s | ' + . '%s | ' + . '|||||
|---|---|---|---|---|---|---|---|---|---|
| %s %s %s | |||||||||
| ' . esc_html($heading) . ' | '; - } - echo '
|---|
' . esc_html__( + '„Zuletzt genutzt“ wird wie bei Anwendungspasswörtern höchstens einmal am Tag aktualisiert.', + 'wp-lit-mcp' + ) . '
'; + } } /** @@ -193,21 +209,42 @@ final class AccessTab implements AdminTab */ private function renderConnectionRow(array $connection): void { + $dateFormat = (string) get_option('date_format', 'd.m.Y'); + $timeFormat = (string) get_option('time_format', 'H:i'); + echo '%s
'
- . '
' . esc_html__( - 'In claude.ai unter Einstellungen → Connectors → „Add custom connector“ eintragen;', + echo '
| %s | %s | %s |
|---|
' . esc_html__('Weitere Prüfungen unter Werkzeuge → Website-Zustand.', 'wp-lit-mcp') . '
'; + echo ''; + printf( + '%s %s.
', + esc_html__('Weitere Prüfungen unter', 'wp-lit-mcp'), + esc_url(admin_url('site-health.php')), + esc_html__('Werkzeuge → Website-Zustand', 'wp-lit-mcp') + ); } } diff --git a/src/Admin/ConnectionUsage.php b/src/Admin/ConnectionUsage.php new file mode 100644 index 0000000..8e51666 --- /dev/null +++ b/src/Admin/ConnectionUsage.php @@ -0,0 +1,81 @@ + */ + private $decode; + + /** @var callable(int, string): mixed */ + private $recordUsage; + + /** + * @param (callable(string): ?array