Initial plugin setup (v0.0.1)
Some checks failed
Create Release Package / build-release (push) Failing after 48s

- Create initial WordPress plugin structure
- Add Prometheus metrics collector with default metrics
- Implement authenticated /metrics endpoint with Bearer token
- Add license management integration
- Create admin settings page under Settings > Metrics
- Set up Gitea CI/CD pipeline for automated releases
- Add extensibility via wp_prometheus_collect_metrics hook

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 15:31:21 +01:00
commit 7ff87f7c8d
21 changed files with 2890 additions and 0 deletions

406
src/Admin/Settings.php Normal file
View File

@@ -0,0 +1,406 @@
<?php
/**
* Admin settings class.
*
* @package WP_Prometheus
*/
namespace Magdev\WpPrometheus\Admin;
use Magdev\WpPrometheus\License\Manager as LicenseManager;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Settings class.
*
* Handles plugin settings page in the WordPress admin.
*/
class Settings {
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
/**
* Add settings page to admin menu.
*
* @return void
*/
public function add_settings_page(): void {
add_options_page(
__( 'Metrics Settings', 'wp-prometheus' ),
__( 'Metrics', 'wp-prometheus' ),
'manage_options',
'wp-prometheus',
array( $this, 'render_settings_page' )
);
}
/**
* Register plugin settings.
*
* @return void
*/
public function register_settings(): void {
// License settings section.
add_settings_section(
'wp_prometheus_license_section',
__( 'License Settings', 'wp-prometheus' ),
array( $this, 'render_license_section' ),
'wp-prometheus'
);
// Auth token section.
add_settings_section(
'wp_prometheus_auth_section',
__( 'Authentication', 'wp-prometheus' ),
array( $this, 'render_auth_section' ),
'wp-prometheus'
);
// Metrics section.
add_settings_section(
'wp_prometheus_metrics_section',
__( 'Default Metrics', 'wp-prometheus' ),
array( $this, 'render_metrics_section' ),
'wp-prometheus'
);
// Register settings.
register_setting( 'wp_prometheus_settings', 'wp_prometheus_auth_token', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
) );
register_setting( 'wp_prometheus_settings', 'wp_prometheus_enabled_metrics', array(
'type' => 'array',
'sanitize_callback' => array( $this, 'sanitize_metrics' ),
) );
// Auth token field.
add_settings_field(
'wp_prometheus_auth_token',
__( 'Auth Token', 'wp-prometheus' ),
array( $this, 'render_auth_token_field' ),
'wp-prometheus',
'wp_prometheus_auth_section'
);
// Enabled metrics field.
add_settings_field(
'wp_prometheus_enabled_metrics',
__( 'Enabled Metrics', 'wp-prometheus' ),
array( $this, 'render_enabled_metrics_field' ),
'wp-prometheus',
'wp_prometheus_metrics_section'
);
}
/**
* Enqueue admin scripts.
*
* @param string $hook_suffix Current admin page.
* @return void
*/
public function enqueue_scripts( string $hook_suffix ): void {
if ( 'settings_page_wp-prometheus' !== $hook_suffix ) {
return;
}
wp_enqueue_script(
'wp-prometheus-admin',
WP_PROMETHEUS_URL . 'assets/js/admin.js',
array( 'jquery' ),
WP_PROMETHEUS_VERSION,
true
);
wp_localize_script( 'wp-prometheus-admin', 'wpPrometheus', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wp_prometheus_license_action' ),
) );
}
/**
* Render the settings page.
*
* @return void
*/
public function render_settings_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Handle license settings save.
if ( isset( $_POST['wp_prometheus_license_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_prometheus_license_nonce'] ), 'wp_prometheus_save_license' ) ) {
LicenseManager::save_settings( array(
'license_key' => isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '',
'server_url' => isset( $_POST['license_server_url'] ) ? esc_url_raw( wp_unslash( $_POST['license_server_url'] ) ) : '',
'server_secret' => isset( $_POST['license_server_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['license_server_secret'] ) ) : '',
) );
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'License settings saved.', 'wp-prometheus' ) . '</p></div>';
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<?php $this->render_license_form(); ?>
<form method="post" action="options.php">
<?php
settings_fields( 'wp_prometheus_settings' );
do_settings_sections( 'wp-prometheus' );
submit_button();
?>
</form>
<?php $this->render_endpoint_info(); ?>
</div>
<?php
}
/**
* Render license settings form.
*
* @return void
*/
private function render_license_form(): void {
$license_key = LicenseManager::get_license_key();
$server_url = LicenseManager::get_server_url();
$license_status = LicenseManager::get_cached_status();
$license_data = LicenseManager::get_cached_data();
$last_check = LicenseManager::get_last_check();
$status_classes = array(
'valid' => 'notice-success',
'invalid' => 'notice-error',
'expired' => 'notice-warning',
'revoked' => 'notice-error',
'inactive' => 'notice-warning',
'unchecked' => 'notice-info',
'unconfigured' => 'notice-info',
);
$status_messages = array(
'valid' => __( 'License is active and valid.', 'wp-prometheus' ),
'invalid' => __( 'License is invalid.', 'wp-prometheus' ),
'expired' => __( 'License has expired.', 'wp-prometheus' ),
'revoked' => __( 'License has been revoked.', 'wp-prometheus' ),
'inactive' => __( 'License is inactive.', 'wp-prometheus' ),
'unchecked' => __( 'License has not been validated yet.', 'wp-prometheus' ),
'unconfigured' => __( 'License server is not configured.', 'wp-prometheus' ),
);
$status_class = $status_classes[ $license_status ] ?? 'notice-info';
$status_message = $status_messages[ $license_status ] ?? __( 'Unknown status.', 'wp-prometheus' );
?>
<div class="wp-prometheus-license-status notice <?php echo esc_attr( $status_class ); ?>" style="padding: 12px;">
<strong><?php echo esc_html( $status_message ); ?></strong>
<?php if ( 'valid' === $license_status && ! empty( $license_data['expires_at'] ) ) : ?>
<br><span class="description">
<?php
printf(
/* translators: %s: Expiration date */
esc_html__( 'Expires: %s', 'wp-prometheus' ),
esc_html( $license_data['expires_at'] )
);
?>
</span>
<?php endif; ?>
<?php if ( $last_check > 0 ) : ?>
<br><span class="description">
<?php
printf(
/* translators: %s: Time ago */
esc_html__( 'Last checked: %s ago', 'wp-prometheus' ),
esc_html( human_time_diff( $last_check, time() ) )
);
?>
</span>
<?php endif; ?>
</div>
<form method="post" action="" id="wp-prometheus-license-form">
<?php wp_nonce_field( 'wp_prometheus_save_license', 'wp_prometheus_license_nonce' ); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="license_server_url"><?php esc_html_e( 'License Server URL', 'wp-prometheus' ); ?></label>
</th>
<td>
<input type="url" name="license_server_url" id="license_server_url"
value="<?php echo esc_attr( $server_url ); ?>"
class="regular-text" placeholder="https://example.com">
</td>
</tr>
<tr>
<th scope="row">
<label for="license_key"><?php esc_html_e( 'License Key', 'wp-prometheus' ); ?></label>
</th>
<td>
<input type="text" name="license_key" id="license_key"
value="<?php echo esc_attr( $license_key ); ?>"
class="regular-text" placeholder="XXXX-XXXX-XXXX-XXXX">
</td>
</tr>
<tr>
<th scope="row">
<label for="license_server_secret"><?php esc_html_e( 'Server Secret', 'wp-prometheus' ); ?></label>
</th>
<td>
<input type="password" name="license_server_secret" id="license_server_secret"
value="" class="regular-text" placeholder="<?php echo esc_attr( ! empty( LicenseManager::get_server_secret() ) ? '••••••••••••••••' : '' ); ?>">
<p class="description"><?php esc_html_e( 'Leave empty to keep existing.', 'wp-prometheus' ); ?></p>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save License Settings', 'wp-prometheus' ), 'primary', 'submit', false ); ?>
<button type="button" id="wp-prometheus-validate-license" class="button button-secondary" style="margin-left: 10px;">
<?php esc_html_e( 'Validate License', 'wp-prometheus' ); ?>
</button>
<button type="button" id="wp-prometheus-activate-license" class="button button-secondary" style="margin-left: 10px;">
<?php esc_html_e( 'Activate License', 'wp-prometheus' ); ?>
</button>
<span id="wp-prometheus-license-spinner" class="spinner" style="float: none;"></span>
</p>
</form>
<div id="wp-prometheus-license-message" style="display: none; margin-top: 10px;"></div>
<hr>
<?php
}
/**
* Render license section description.
*
* @return void
*/
public function render_license_section(): void {
// License section rendered separately in render_license_form().
}
/**
* Render auth section description.
*
* @return void
*/
public function render_auth_section(): void {
echo '<p>' . esc_html__( 'Configure authentication for the /metrics endpoint.', 'wp-prometheus' ) . '</p>';
}
/**
* Render metrics section description.
*
* @return void
*/
public function render_metrics_section(): void {
echo '<p>' . esc_html__( 'Select which default metrics to expose.', 'wp-prometheus' ) . '</p>';
}
/**
* Render auth token field.
*
* @return void
*/
public function render_auth_token_field(): void {
$token = get_option( 'wp_prometheus_auth_token', '' );
?>
<input type="text" name="wp_prometheus_auth_token" id="wp_prometheus_auth_token"
value="<?php echo esc_attr( $token ); ?>" class="regular-text" readonly>
<button type="button" id="wp-prometheus-regenerate-token" class="button button-secondary">
<?php esc_html_e( 'Regenerate', 'wp-prometheus' ); ?>
</button>
<p class="description">
<?php esc_html_e( 'Use this token to authenticate Prometheus scrape requests.', 'wp-prometheus' ); ?>
</p>
<?php
}
/**
* Render enabled metrics field.
*
* @return void
*/
public function render_enabled_metrics_field(): void {
$enabled = get_option( 'wp_prometheus_enabled_metrics', array() );
$metrics = array(
'wordpress_info' => __( 'WordPress Info (version, PHP version, multisite)', 'wp-prometheus' ),
'wordpress_users_total' => __( 'Total Users by Role', 'wp-prometheus' ),
'wordpress_posts_total' => __( 'Total Posts by Type and Status', 'wp-prometheus' ),
'wordpress_comments_total' => __( 'Total Comments by Status', 'wp-prometheus' ),
'wordpress_plugins_total' => __( 'Total Plugins (active/inactive)', 'wp-prometheus' ),
);
foreach ( $metrics as $key => $label ) {
?>
<label style="display: block; margin-bottom: 5px;">
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
value="<?php echo esc_attr( $key ); ?>"
<?php checked( in_array( $key, $enabled, true ) ); ?>>
<?php echo esc_html( $label ); ?>
</label>
<?php
}
}
/**
* Render endpoint info.
*
* @return void
*/
private function render_endpoint_info(): void {
$token = get_option( 'wp_prometheus_auth_token', '' );
$endpoint_url = home_url( '/metrics/' );
?>
<hr>
<h2><?php esc_html_e( 'Prometheus Configuration', 'wp-prometheus' ); ?></h2>
<p><?php esc_html_e( 'Add the following to your prometheus.yml:', 'wp-prometheus' ); ?></p>
<pre style="background: #f1f1f1; padding: 15px; overflow-x: auto;">
scrape_configs:
- job_name: 'wordpress'
static_configs:
- targets: ['<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_HOST ) ); ?>']
metrics_path: '/metrics/'
scheme: '<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_SCHEME ) ); ?>'
authorization:
type: Bearer
credentials: '<?php echo esc_html( $token ); ?>'</pre>
<p>
<?php
printf(
/* translators: %s: Endpoint URL */
esc_html__( 'Metrics endpoint: %s', 'wp-prometheus' ),
'<code>' . esc_url( $endpoint_url ) . '</code>'
);
?>
</p>
<?php
}
/**
* Sanitize enabled metrics.
*
* @param mixed $input Input value.
* @return array
*/
public function sanitize_metrics( $input ): array {
if ( ! is_array( $input ) ) {
return array();
}
return array_map( 'sanitize_text_field', $input );
}
}