4 Commits

Author SHA1 Message Date
bad977bef0 feat: Add custom metric builder, export/import, and Grafana dashboards (v0.3.0)
All checks were successful
Create Release Package / build-release (push) Successful in 59s
- Custom Metrics Builder with admin UI for gauge metrics
- Support for static values and WordPress option-based values
- JSON-based export/import with skip/overwrite/rename modes
- Three Grafana dashboard templates (overview, runtime, WooCommerce)
- New tabs: Custom Metrics and Dashboards
- Reset runtime metrics button

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 15:27:16 +01:00
da6d5081f7 fix: Localhost license bypass, rewrite rules flush, WooCommerce orders (v0.2.2)
All checks were successful
Create Release Package / build-release (push) Successful in 56s
- Add localhost license bypass for development environments
- Flush rewrite rules when license status changes to fix 404 on /metrics
- Fix wc_orders_count() missing required status parameter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:57:43 +01:00
3eb66b0ebe feat: Add WooCommerce, cron, and transient metrics (v0.2.0)
All checks were successful
Create Release Package / build-release (push) Successful in 56s
- WooCommerce integration metrics (products, orders, revenue, customers)
- Cron job metrics (events by hook, overdue count, next run timestamp)
- Transient cache metrics (total, expiring, expired)
- Support for WooCommerce HPOS storage
- Updated settings page with new metric categories
- Updated translations and documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:41:09 +01:00
bc108f6bd5 feat: Reorganize settings page with tabbed interface (v0.1.1)
All checks were successful
Create Release Package / build-release (push) Successful in 57s
- Add tabbed navigation (License, Metrics, Help)
- Move Prometheus configuration to dedicated Help tab
- Separate static and runtime metrics with descriptions
- Add admin CSS for tab styling
- Add endpoint info, curl examples, and metrics reference in Help tab

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:30:11 +01:00
19 changed files with 7358 additions and 609 deletions

View File

@@ -5,6 +5,86 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.0] - 2026-02-02
### Added
- Custom Metrics Builder:
- Admin UI to define custom gauge metrics
- Support for static values and WordPress option-based values
- Label support with up to 5 labels and 50 value combinations
- Metric validation following Prometheus naming conventions
- Metric Export/Import:
- JSON-based configuration export for backup
- Import with three modes: skip existing, overwrite, or rename duplicates
- Version tracking in export format
- Grafana Dashboard Templates:
- WordPress Overview dashboard (users, posts, comments, cron, transients)
- WordPress Runtime dashboard (HTTP requests, database queries)
- WordPress WooCommerce dashboard (orders, revenue, products, customers)
- Easy download and import instructions
- New "Custom Metrics" tab in admin settings
- New "Dashboards" tab in admin settings
- Reset runtime metrics button to clear accumulated data
### Changed
- Settings page now has 5 tabs: License, Metrics, Custom Metrics, Dashboards, Help
- Updated translations with all new strings
## [0.2.2] - 2026-02-02
### Fixed
- Fixed `wc_orders_count()` call missing required status parameter in WooCommerce orders metrics
## [0.2.1] - 2026-02-02
### Added
- Localhost license bypass for development environments (localhost, 127.0.0.1, ::1, \*.localhost, \*.local)
- Automatic rewrite rules flush when license status changes
### Fixed
- Fixed 404 error on `/metrics` endpoint when license becomes valid after plugin activation
## [0.2.0] - 2026-02-02
### Added
- WooCommerce integration metrics (when WooCommerce is active):
- `wordpress_woocommerce_products_total` - Products by status and type
- `wordpress_woocommerce_orders_total` - Orders by status
- `wordpress_woocommerce_revenue_total` - Revenue (all time, today, month)
- `wordpress_woocommerce_customers_total` - Customers (registered, guest)
- Cron job metrics:
- `wordpress_cron_events_total` - Scheduled cron events by hook
- `wordpress_cron_overdue_total` - Number of overdue cron events
- `wordpress_cron_next_run_timestamp` - Unix timestamp of next scheduled cron
- Transient cache metrics:
- `wordpress_transients_total` - Transients by type (total, with_expiration, persistent, expired)
- WooCommerce metrics section in settings (only visible when WooCommerce is active)
- Support for WooCommerce HPOS (High-Performance Order Storage)
### Changed
- Updated Help tab with new metrics reference
## [0.1.1] - 2026-02-02
### Changed
- Reorganized settings page with tabbed interface (License, Metrics, Help tabs)
- Moved Prometheus configuration help to dedicated Help tab
- Separated static and runtime metrics in settings with descriptions
- Added admin CSS for improved tab styling
### Added
- New Help tab with endpoint information, curl examples, and metrics reference table
- Custom code examples section in Help tab
## [0.1.0] - 2026-02-02 ## [0.1.0] - 2026-02-02
### Added ### Added

View File

@@ -15,6 +15,12 @@ This plugin provides a Prometheus `/metrics` endpoint and an extensible way to a
- Prometheus compatible authenticated `/metrics` endpoint - Prometheus compatible authenticated `/metrics` endpoint
- Optional default metrics (users, posts, comments, plugins) - Optional default metrics (users, posts, comments, plugins)
- Runtime metrics (HTTP requests, request duration, database queries) - Runtime metrics (HTTP requests, request duration, database queries)
- Cron job metrics (scheduled events, overdue, next run)
- Transient cache metrics (total, expiring, expired)
- WooCommerce integration (products, orders, revenue, customers)
- Custom metric builder with admin UI (gauges with static or option-based values)
- Metric export/import for backup and site migration
- Grafana dashboard templates for easy visualization
- Dedicated plugin settings under 'Settings/Metrics' menu - Dedicated plugin settings under 'Settings/Metrics' menu
- Extensible by other plugins using `wp_prometheus_collect_metrics` action hook - Extensible by other plugins using `wp_prometheus_collect_metrics` action hook
- License management integration - License management integration
@@ -27,11 +33,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. **Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file.
### Version 0.2.0 (Planned) *No planned features at this time.*
- WooCommerce integration metrics
- Cron job metrics
- Transient cache metrics
## Technical Stack ## Technical Stack
@@ -211,6 +213,10 @@ wp-prometheus/
│ └── release.yml # CI/CD pipeline │ └── release.yml # CI/CD pipeline
├── assets/ ├── assets/
│ ├── css/ # Admin/Frontend styles │ ├── css/ # Admin/Frontend styles
│ ├── dashboards/ # Grafana dashboard templates
│ │ ├── wordpress-overview.json
│ │ ├── wordpress-runtime.json
│ │ └── wordpress-woocommerce.json
│ └── js/ │ └── js/
│ └── admin.js # Admin JavaScript │ └── admin.js # Admin JavaScript
├── languages/ # Translation files ├── languages/ # Translation files
@@ -219,6 +225,7 @@ wp-prometheus/
├── releases/ # Release packages ├── releases/ # Release packages
├── src/ ├── src/
│ ├── Admin/ │ ├── Admin/
│ │ ├── DashboardProvider.php # Grafana dashboard provider
│ │ └── Settings.php # Settings page │ │ └── Settings.php # Settings page
│ ├── Endpoint/ │ ├── Endpoint/
│ │ └── MetricsEndpoint.php # /metrics endpoint │ │ └── MetricsEndpoint.php # /metrics endpoint
@@ -226,6 +233,7 @@ wp-prometheus/
│ │ └── Manager.php # License management │ │ └── Manager.php # License management
│ ├── Metrics/ │ ├── Metrics/
│ │ ├── Collector.php # Prometheus metrics collector │ │ ├── Collector.php # Prometheus metrics collector
│ │ ├── CustomMetricBuilder.php # Custom metric CRUD
│ │ └── RuntimeCollector.php # Runtime metrics collector │ │ └── RuntimeCollector.php # Runtime metrics collector
│ ├── Installer.php # Activation/Deactivation │ ├── Installer.php # Activation/Deactivation
│ ├── Plugin.php # Main plugin class │ ├── Plugin.php # Main plugin class
@@ -282,6 +290,59 @@ add_action( 'wp_prometheus_collect_metrics', function( $collector ) {
## Session History ## Session History
### 2026-02-02 - Custom Metrics & Dashboards (v0.3.0)
- Added Custom Metric Builder with full admin UI:
- `CustomMetricBuilder.php` - CRUD operations, validation, export/import
- Support for static values and WordPress option-based values
- Label support (max 5 labels, 50 value combinations)
- Prometheus naming convention validation (`[a-zA-Z_:][a-zA-Z0-9_:]*`)
- Added Grafana Dashboard Templates:
- `DashboardProvider.php` - Dashboard file provider with path traversal protection
- `wordpress-overview.json` - General WordPress metrics
- `wordpress-runtime.json` - HTTP/DB performance metrics
- `wordpress-woocommerce.json` - WooCommerce store metrics
- Added export/import functionality:
- JSON-based configuration export
- Three import modes: skip, overwrite, rename duplicates
- Version tracking in export format
- Updated Settings page with new tabs:
- "Custom Metrics" tab with metric form and table
- "Dashboards" tab with download buttons
- "Reset Runtime Metrics" button in Metrics tab
- Updated `Collector.php` to integrate custom metrics
- Updated translation files with all new strings
- **Key Learning**: Dynamic form handling in WordPress admin
- Use `wp_create_nonce()` with unique nonce names per AJAX action
- Localize script with `wp_localize_script()` for nonces and AJAX URL
- Always verify `current_user_can('manage_options')` in AJAX handlers
- **Key Learning**: Grafana dashboard JSON format
- Use `${DS_PROMETHEUS}` for data source variable
- Schema version 39 for current Grafana compatibility
- Panels use `gridPos` for layout positioning
### 2026-02-02 - Extended Metrics (v0.2.0)
- Added WooCommerce integration metrics (only when WooCommerce is active):
- `wordpress_woocommerce_products_total` - Products by status and type
- `wordpress_woocommerce_orders_total` - Orders by status
- `wordpress_woocommerce_revenue_total` - Revenue (all time, today, month)
- `wordpress_woocommerce_customers_total` - Customers (registered, guest)
- Added cron job metrics:
- `wordpress_cron_events_total` - Scheduled cron events by hook (top 20)
- `wordpress_cron_overdue_total` - Number of overdue cron events
- `wordpress_cron_next_run_timestamp` - Unix timestamp of next scheduled cron
- Added transient cache metrics:
- `wordpress_transients_total` - Transients by type (total, with_expiration, persistent, expired)
- Updated Settings page with new metric categories
- Updated Help tab with new metrics reference
- **Key Learning**: WooCommerce HPOS (High-Performance Order Storage) requires different queries
- Check `OrderUtil::custom_orders_table_usage_is_enabled()` to determine storage type
- HPOS uses `wc_orders` table instead of `posts` and `postmeta`
- **Key Learning**: Cron event labeling requires cardinality control
- Limit to top 20 hooks to prevent label explosion
- Use `arsort()` to get most frequent hooks first
### 2026-02-02 - Runtime Metrics (v0.1.0) ### 2026-02-02 - Runtime Metrics (v0.1.0)
- Implemented runtime metrics collection for HTTP requests and database queries - Implemented runtime metrics collection for HTTP requests and database queries

View File

@@ -161,12 +161,6 @@ https://example.com/metrics/?token=your-auth-token
## Future Enhancements ## Future Enhancements
### Version 0.2.0
- WooCommerce integration metrics
- Cron job metrics
- Transient cache metrics
### Version 0.3.0 ### Version 0.3.0
- Custom metric builder in admin - Custom metric builder in admin

View File

@@ -92,6 +92,33 @@ scrape_configs:
**Note:** Runtime metrics aggregate data across requests. Enable only the metrics you need to minimize performance impact. **Note:** Runtime metrics aggregate data across requests. Enable only the metrics you need to minimize performance impact.
### Cron Metrics (v0.2.0+)
| Metric | Type | Labels | Description |
|--------|------|--------|-------------|
| wordpress_cron_events_total | Gauge | hook | Scheduled cron events by hook |
| wordpress_cron_overdue_total | Gauge | - | Number of overdue cron events |
| wordpress_cron_next_run_timestamp | Gauge | - | Unix timestamp of next scheduled cron |
### Transient Metrics (v0.2.0+)
| Metric | Type | Labels | Description |
|--------|------|--------|-------------|
| wordpress_transients_total | Gauge | type | Transients by type (total, with_expiration, persistent, expired) |
### WooCommerce Metrics (v0.2.0+)
These metrics are only available when WooCommerce is active.
| Metric | Type | Labels | Description |
|--------|------|--------|-------------|
| wordpress_woocommerce_products_total | Gauge | status, type | Products by status and type |
| wordpress_woocommerce_orders_total | Gauge | status | Orders by status |
| wordpress_woocommerce_revenue_total | Gauge | period, currency | Revenue (all_time, today, month) |
| wordpress_woocommerce_customers_total | Gauge | type | Customers (registered, guest) |
**Note:** WooCommerce metrics support both legacy post-based orders and HPOS (High-Performance Order Storage).
## Extending with Custom Metrics ## Extending with Custom Metrics
Add your own metrics using the `wp_prometheus_collect_metrics` action: Add your own metrics using the `wp_prometheus_collect_metrics` action:

207
assets/css/admin.css Normal file
View File

@@ -0,0 +1,207 @@
/**
* WP Prometheus Admin Styles
*
* @package WP_Prometheus
*/
/* Tab content styling */
.wp-prometheus-tab-content {
margin-top: 20px;
}
/* License status box */
.wp-prometheus-license-status {
margin: 15px 0;
}
/* Help tab code blocks */
.wp-prometheus-tab-content pre {
background: #f1f1f1;
padding: 15px;
overflow-x: auto;
margin: 15px 0;
border-radius: 3px;
border: 1px solid #ddd;
}
/* Help tab tables */
.wp-prometheus-tab-content .widefat {
margin: 15px 0;
}
.wp-prometheus-tab-content .widefat code {
background: none;
padding: 0;
}
/* Metrics fieldset */
.wp-prometheus-tab-content fieldset p strong {
display: block;
margin-bottom: 8px;
font-size: 14px;
}
/* Form table adjustments for tabs */
.wp-prometheus-tab-content .form-table {
margin-top: 10px;
}
/* Custom metrics form */
.wp-prometheus-metric-form {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
padding: 20px;
margin: 20px 0;
}
.wp-prometheus-metric-form h3 {
margin-top: 0;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.wp-prometheus-metric-form .required {
color: #d63638;
}
/* Label rows */
.metric-label-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.metric-label-row input {
flex: 1;
max-width: 300px;
}
.metric-label-row .remove-label {
padding: 0 8px;
min-width: auto;
}
/* Value rows */
.metric-value-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.metric-value-row input {
width: 120px;
}
.metric-value-row .remove-value-row {
padding: 0 8px;
min-width: auto;
}
/* Custom metrics table */
.wp-prometheus-custom-metrics .wp-list-table code {
background: #f0f0f1;
padding: 2px 6px;
border-radius: 3px;
}
.wp-prometheus-custom-metrics .wp-list-table td {
vertical-align: middle;
}
.wp-prometheus-custom-metrics .wp-list-table .dashicons {
font-size: 18px;
width: 18px;
height: 18px;
}
/* Dashboard grid */
.wp-prometheus-dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin: 20px 0;
}
.wp-prometheus-dashboard-card {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
padding: 20px;
text-align: center;
transition: box-shadow 0.2s ease;
}
.wp-prometheus-dashboard-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.wp-prometheus-dashboard-card .dashboard-icon {
margin-bottom: 15px;
}
.wp-prometheus-dashboard-card .dashboard-icon .dashicons {
font-size: 48px;
width: 48px;
height: 48px;
color: #2271b1;
}
.wp-prometheus-dashboard-card h3 {
margin: 0 0 10px 0;
font-size: 16px;
}
.wp-prometheus-dashboard-card p {
color: #646970;
margin: 0 0 15px 0;
font-size: 13px;
}
/* Import options panel */
#import-options {
border-radius: 4px;
}
#import-options label {
display: block;
margin-bottom: 8px;
}
/* Spinner alignment */
.spinner {
float: none;
vertical-align: middle;
}
/* Button groups */
.wp-prometheus-tab-content .button + .button {
margin-left: 5px;
}
/* Notice messages in forms */
.wp-prometheus-metric-form .notice,
.wp-prometheus-custom-metrics .notice {
margin: 10px 0;
padding: 10px 15px;
}
/* Responsive adjustments */
@media screen and (max-width: 782px) {
.wp-prometheus-dashboard-grid {
grid-template-columns: 1fr;
}
.metric-value-row {
flex-direction: column;
align-items: flex-start;
}
.metric-value-row input {
width: 100%;
max-width: none;
}
}

View File

@@ -0,0 +1,851 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"panels": [],
"title": "System Info",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 1
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "/^version$/",
"values": false
},
"textMode": "value"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_info",
"format": "table",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "WordPress Version",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 1
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "/^php_version$/",
"values": false
},
"textMode": "value"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_info",
"format": "table",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "PHP Version",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 1
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_users_total)",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Total Users",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 1
},
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_plugins_total{status=\"active\"}",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Active Plugins",
"type": "stat"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 5
},
"id": 6,
"panels": [],
"title": "Content",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 0,
"y": 6
},
"id": 7,
"options": {
"legend": {
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"pieType": "pie",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_users_total",
"instant": true,
"legendFormat": "{{role}}",
"refId": "A"
}
],
"title": "Users by Role",
"type": "piechart"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 8,
"y": 6
},
"id": 8,
"options": {
"legend": {
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"pieType": "pie",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_posts_total{post_type=\"post\"}",
"instant": true,
"legendFormat": "{{status}}",
"refId": "A"
}
],
"title": "Posts by Status",
"type": "piechart"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 16,
"y": 6
},
"id": 9,
"options": {
"legend": {
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"pieType": "pie",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_comments_total{status!=\"total_comments\"}",
"instant": true,
"legendFormat": "{{status}}",
"refId": "A"
}
],
"title": "Comments by Status",
"type": "piechart"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 14
},
"id": 10,
"panels": [],
"title": "System Health",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 5
},
{
"color": "red",
"value": 10
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 15
},
"id": 11,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_cron_overdue_total",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Overdue Cron Events",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 15
},
"id": 12,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_cron_events_total)",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Total Cron Events",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 15
},
"id": 13,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_transients_total{type=\"total\"}",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Total Transients",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 50
},
{
"color": "red",
"value": 100
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 15
},
"id": 14,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_transients_total{type=\"expired\"}",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Expired Transients",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "bars",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 19
},
"id": 15,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "topk(10, wordpress_cron_events_total)",
"instant": false,
"legendFormat": "{{hook}}",
"range": true,
"refId": "A"
}
],
"title": "Top 10 Cron Hooks",
"type": "timeseries"
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["wordpress", "prometheus"],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"label": "Data Source",
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"queryValue": "",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "WordPress Overview",
"uid": "wp-prometheus-overview",
"version": 1,
"weekStart": ""
}

View File

@@ -0,0 +1,987 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"panels": [],
"title": "HTTP Requests",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "smooth",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "reqps"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 1
},
"id": 2,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(wordpress_http_requests_total[5m])) by (endpoint)",
"legendFormat": "{{endpoint}}",
"range": true,
"refId": "A"
}
],
"title": "Requests per Second by Endpoint",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "smooth",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "reqps"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/2..$/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "green",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/4..$/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "orange",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/5..$/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 1
},
"id": 3,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(wordpress_http_requests_total[5m])) by (status)",
"legendFormat": "{{status}}",
"range": true,
"refId": "A"
}
],
"title": "Requests per Second by Status",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 0,
"y": 9
},
"id": 4,
"options": {
"legend": {
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"pieType": "donut",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_http_requests_total) by (method)",
"instant": true,
"legendFormat": "{{method}}",
"refId": "A"
}
],
"title": "Requests by Method",
"type": "piechart"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 8,
"y": 9
},
"id": 5,
"options": {
"legend": {
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"pieType": "donut",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_http_requests_total) by (endpoint)",
"instant": true,
"legendFormat": "{{endpoint}}",
"refId": "A"
}
],
"title": "Requests by Endpoint",
"type": "piechart"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 16,
"y": 9
},
"id": 6,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_http_requests_total)",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Total Requests (All Time)",
"type": "stat"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 17
},
"id": 7,
"panels": [],
"title": "Request Duration",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "smooth",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 18
},
"id": 8,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_http_request_duration_seconds_sum / wordpress_http_request_duration_seconds_count",
"legendFormat": "{{method}} {{endpoint}}",
"range": true,
"refId": "A"
}
],
"title": "Average Request Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 18
},
"id": 9,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "horizontal",
"showValue": "auto",
"stacking": "none",
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_http_request_duration_seconds_bucket) by (le)",
"format": "table",
"instant": true,
"legendFormat": "{{le}}",
"refId": "A"
}
],
"title": "Request Duration Distribution (Buckets)",
"type": "barchart"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 26
},
"id": 10,
"panels": [],
"title": "Database Queries",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "smooth",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 27
},
"id": 11,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(wordpress_db_queries_total[5m])",
"legendFormat": "{{endpoint}}",
"range": true,
"refId": "A"
}
],
"title": "Database Queries per Second",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "smooth",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 27
},
"id": 12,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "wordpress_db_query_duration_seconds_sum / wordpress_db_query_duration_seconds_count",
"legendFormat": "{{endpoint}}",
"range": true,
"refId": "A"
}
],
"title": "Average Query Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 12,
"x": 0,
"y": 35
},
"id": 13,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_db_queries_total)",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Total Database Queries (All Time)",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 0.1
},
{
"color": "red",
"value": 0.5
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 12,
"x": 12,
"y": 35
},
"id": 14,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(wordpress_db_query_duration_seconds_sum) / sum(wordpress_db_query_duration_seconds_count)",
"instant": true,
"legendFormat": "__auto",
"refId": "A"
}
],
"title": "Average Query Duration (Overall)",
"type": "stat"
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["wordpress", "prometheus", "performance"],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"label": "Data Source",
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"queryValue": "",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "WordPress Runtime Performance",
"uid": "wp-prometheus-runtime",
"version": 1,
"weekStart": ""
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,26 @@
(function($) { (function($) {
'use strict'; 'use strict';
var importFileContent = null;
$(document).ready(function() { $(document).ready(function() {
// License tab handlers.
initLicenseHandlers();
// Custom metrics tab handlers.
initCustomMetricsHandlers();
// Dashboards tab handlers.
initDashboardsHandlers();
// Runtime metrics reset handler.
initResetRuntimeHandler();
});
/**
* Initialize license tab handlers.
*/
function initLicenseHandlers() {
// Validate license button. // Validate license button.
$('#wp-prometheus-validate-license').on('click', function(e) { $('#wp-prometheus-validate-license').on('click', function(e) {
e.preventDefault(); e.preventDefault();
@@ -23,11 +42,171 @@
// Regenerate token button. // Regenerate token button.
$('#wp-prometheus-regenerate-token').on('click', function(e) { $('#wp-prometheus-regenerate-token').on('click', function(e) {
e.preventDefault(); e.preventDefault();
if (confirm('Are you sure you want to regenerate the auth token? You will need to update your Prometheus configuration.')) { if (confirm(wpPrometheus.confirmRegenerateToken)) {
var newToken = generateToken(32); var newToken = generateToken(32);
$('#wp_prometheus_auth_token').val(newToken); $('#wp_prometheus_auth_token').val(newToken);
} }
}); });
}
/**
* Initialize custom metrics tab handlers.
*/
function initCustomMetricsHandlers() {
var $formContainer = $('#wp-prometheus-metric-form-container');
var $form = $('#wp-prometheus-metric-form');
var $showFormBtn = $('#show-metric-form');
// Show metric form.
$showFormBtn.on('click', function() {
resetMetricForm();
$formContainer.slideDown();
$(this).hide();
});
// Cancel metric form.
$('#cancel-metric-form').on('click', function() {
$formContainer.slideUp();
$showFormBtn.show();
// Remove edit parameter from URL.
if (window.location.search.indexOf('edit=') > -1) {
window.history.pushState({}, '', window.location.pathname + '?page=wp-prometheus&tab=custom');
}
});
// Value type toggle.
$('input[name="value_type"]').on('change', function() {
var valueType = $(this).val();
if (valueType === 'option') {
$('#option-config-row').show();
$('#static-values-row').hide();
} else {
$('#option-config-row').hide();
$('#static-values-row').show();
}
});
// Add label.
$('#add-label').on('click', function() {
var $container = $('#metric-labels-container');
var labelCount = $container.find('.metric-label-row').length;
if (labelCount >= 5) {
alert('Maximum 5 labels allowed per metric.');
return;
}
var $row = $('<div class="metric-label-row">' +
'<input type="text" name="labels[]" class="regular-text" placeholder="label_name" pattern="[a-zA-Z_][a-zA-Z0-9_]*">' +
'<button type="button" class="button remove-label">&times;</button>' +
'</div>');
$container.append($row);
updateValueRows();
});
// Remove label.
$(document).on('click', '.remove-label', function() {
$(this).closest('.metric-label-row').remove();
updateValueRows();
});
// Add value row.
$('#add-value-row').on('click', function() {
var $container = $('#metric-values-container');
var rowCount = $container.find('.metric-value-row').length;
var labelCount = getLabelCount();
var $row = $('<div class="metric-value-row"></div>');
// Add label value inputs.
var labels = getLabels();
for (var i = 0; i < labelCount; i++) {
$row.append('<input type="text" name="label_values[' + rowCount + '][]" class="small-text" placeholder="' + (labels[i] || 'value') + '">');
}
// Add metric value input.
$row.append('<input type="number" name="label_values[' + rowCount + '][]" class="small-text" step="any" placeholder="Value">');
$row.append('<button type="button" class="button remove-value-row">&times;</button>');
$container.append($row);
});
// Remove value row.
$(document).on('click', '.remove-value-row', function() {
$(this).closest('.metric-value-row').remove();
});
// Submit metric form.
$form.on('submit', function(e) {
e.preventDefault();
saveCustomMetric();
});
// Delete metric.
$(document).on('click', '.delete-metric', function() {
var id = $(this).data('id');
if (confirm(wpPrometheus.confirmDelete)) {
deleteCustomMetric(id);
}
});
// Export metrics.
$('#export-metrics').on('click', function() {
exportMetrics();
});
// Import metrics - trigger file input.
$('#import-metrics-btn').on('click', function() {
$('#import-metrics-file').click();
});
// Import file selected.
$('#import-metrics-file').on('change', function(e) {
var file = e.target.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function(e) {
importFileContent = e.target.result;
$('#import-options').slideDown();
};
reader.readAsText(file);
}
});
// Confirm import.
$('#confirm-import').on('click', function() {
var mode = $('input[name="import_mode"]:checked').val();
importMetrics(importFileContent, mode);
});
// Cancel import.
$('#cancel-import').on('click', function() {
$('#import-options').slideUp();
$('#import-metrics-file').val('');
importFileContent = null;
});
}
/**
* Initialize dashboards tab handlers.
*/
function initDashboardsHandlers() {
$(document).on('click', '.download-dashboard', function() {
var slug = $(this).data('slug');
downloadDashboard(slug);
});
}
/**
* Initialize reset runtime metrics handler.
*/
function initResetRuntimeHandler() {
$('#wp-prometheus-reset-runtime').on('click', function() {
if (confirm(wpPrometheus.confirmReset)) {
resetRuntimeMetrics();
}
});
}
/** /**
* Perform a license action via AJAX. * Perform a license action via AJAX.
@@ -82,6 +261,325 @@
}); });
} }
/**
* Save custom metric via AJAX.
*/
function saveCustomMetric() {
var $spinner = $('#wp-prometheus-metric-spinner');
var $message = $('#wp-prometheus-metric-message');
var $form = $('#wp-prometheus-metric-form');
$spinner.addClass('is-active');
$message.hide();
var formData = $form.serialize();
formData += '&action=wp_prometheus_save_custom_metric';
formData += '&nonce=' + wpPrometheus.customMetricNonce;
$.ajax({
url: wpPrometheus.ajaxUrl,
type: 'POST',
data: formData,
success: function(response) {
$spinner.removeClass('is-active');
if (response.success) {
$message
.removeClass('notice-error')
.addClass('notice notice-success')
.html('<p>' + response.data.message + '</p>')
.show();
setTimeout(function() {
window.location.href = window.location.pathname + '?page=wp-prometheus&tab=custom';
}, 1000);
} else {
$message
.removeClass('notice-success')
.addClass('notice notice-error')
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
.show();
}
},
error: function() {
$spinner.removeClass('is-active');
$message
.removeClass('notice-success')
.addClass('notice notice-error')
.html('<p>Connection error. Please try again.</p>')
.show();
}
});
}
/**
* Delete custom metric via AJAX.
*
* @param {string} id Metric ID.
*/
function deleteCustomMetric(id) {
$.ajax({
url: wpPrometheus.ajaxUrl,
type: 'POST',
data: {
action: 'wp_prometheus_delete_custom_metric',
nonce: wpPrometheus.customMetricNonce,
id: id
},
success: function(response) {
if (response.success) {
$('tr[data-metric-id="' + id + '"]').fadeOut(function() {
$(this).remove();
// Check if table is empty.
if ($('.wp-prometheus-custom-metrics tbody tr').length === 0) {
location.reload();
}
});
} else {
alert(response.data.message || 'An error occurred.');
}
},
error: function() {
alert('Connection error. Please try again.');
}
});
}
/**
* Export metrics via AJAX.
*/
function exportMetrics() {
$.ajax({
url: wpPrometheus.ajaxUrl,
type: 'POST',
data: {
action: 'wp_prometheus_export_metrics',
nonce: wpPrometheus.exportNonce
},
success: function(response) {
if (response.success) {
downloadFile(response.data.json, response.data.filename, 'application/json');
} else {
alert(response.data.message || 'An error occurred.');
}
},
error: function() {
alert('Connection error. Please try again.');
}
});
}
/**
* Import metrics via AJAX.
*
* @param {string} json JSON content.
* @param {string} mode Import mode.
*/
function importMetrics(json, mode) {
var $spinner = $('#wp-prometheus-import-spinner');
var $message = $('#wp-prometheus-import-message');
$spinner.addClass('is-active');
$message.hide();
$.ajax({
url: wpPrometheus.ajaxUrl,
type: 'POST',
data: {
action: 'wp_prometheus_import_metrics',
nonce: wpPrometheus.importNonce,
json: json,
mode: mode
},
success: function(response) {
$spinner.removeClass('is-active');
if (response.success) {
$message
.removeClass('notice-error')
.addClass('notice notice-success')
.html('<p>' + response.data.message + '</p>')
.show();
$('#import-options').slideUp();
$('#import-metrics-file').val('');
importFileContent = null;
setTimeout(function() {
location.reload();
}, 1500);
} else {
$message
.removeClass('notice-success')
.addClass('notice notice-error')
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
.show();
}
},
error: function() {
$spinner.removeClass('is-active');
$message
.removeClass('notice-success')
.addClass('notice notice-error')
.html('<p>Connection error. Please try again.</p>')
.show();
}
});
}
/**
* Download dashboard via AJAX.
*
* @param {string} slug Dashboard slug.
*/
function downloadDashboard(slug) {
$.ajax({
url: wpPrometheus.ajaxUrl,
type: 'POST',
data: {
action: 'wp_prometheus_download_dashboard',
nonce: wpPrometheus.dashboardNonce,
slug: slug
},
success: function(response) {
if (response.success) {
downloadFile(response.data.json, response.data.filename, 'application/json');
} else {
alert(response.data.message || 'An error occurred.');
}
},
error: function() {
alert('Connection error. Please try again.');
}
});
}
/**
* Reset runtime metrics via AJAX.
*/
function resetRuntimeMetrics() {
var $spinner = $('#wp-prometheus-reset-spinner');
var $message = $('#wp-prometheus-reset-message');
$spinner.addClass('is-active');
$message.hide();
$.ajax({
url: wpPrometheus.ajaxUrl,
type: 'POST',
data: {
action: 'wp_prometheus_reset_runtime_metrics',
nonce: wpPrometheus.resetRuntimeNonce
},
success: function(response) {
$spinner.removeClass('is-active');
if (response.success) {
$message
.removeClass('notice-error')
.addClass('notice notice-success')
.html('<p>' + response.data.message + '</p>')
.show();
} else {
$message
.removeClass('notice-success')
.addClass('notice notice-error')
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
.show();
}
},
error: function() {
$spinner.removeClass('is-active');
$message
.removeClass('notice-success')
.addClass('notice notice-error')
.html('<p>Connection error. Please try again.</p>')
.show();
}
});
}
/**
* Reset the metric form to default state.
*/
function resetMetricForm() {
var $form = $('#wp-prometheus-metric-form');
$form[0].reset();
$('#metric-id').val('');
$('#wp-prometheus-form-title').text('Add New Metric');
$('#option-config-row').hide();
$('#static-values-row').show();
$('#wp-prometheus-metric-message').hide();
// Reset labels to single empty row.
$('#metric-labels-container').html(
'<div class="metric-label-row">' +
'<input type="text" name="labels[]" class="regular-text" placeholder="label_name" pattern="[a-zA-Z_][a-zA-Z0-9_]*">' +
'<button type="button" class="button remove-label">&times;</button>' +
'</div>'
);
// Reset values to single empty row.
$('#metric-values-container').html(
'<div class="metric-value-row">' +
'<input type="number" name="label_values[0][]" class="small-text" step="any" placeholder="Value">' +
'<button type="button" class="button remove-value-row">&times;</button>' +
'</div>'
);
}
/**
* Update value rows when labels change.
*/
function updateValueRows() {
var labels = getLabels();
var labelCount = labels.length;
$('#metric-values-container .metric-value-row').each(function(rowIndex) {
var $row = $(this);
var inputs = $row.find('input').toArray();
var currentValues = inputs.map(function(input) { return input.value; });
// Remove all inputs except the value and button.
$row.find('input').remove();
// Re-add label inputs.
for (var i = 0; i < labelCount; i++) {
var val = currentValues[i] || '';
$row.prepend('<input type="text" name="label_values[' + rowIndex + '][]" class="small-text" placeholder="' + (labels[i] || 'value') + '" value="' + val + '">');
}
// Re-add value input.
var metricVal = currentValues[currentValues.length - 1] || '';
$row.find('.remove-value-row').before('<input type="number" name="label_values[' + rowIndex + '][]" class="small-text" step="any" placeholder="Value" value="' + metricVal + '">');
});
}
/**
* Get current label names.
*
* @return {string[]} Array of label names.
*/
function getLabels() {
var labels = [];
$('#metric-labels-container input[name="labels[]"]').each(function() {
var val = $(this).val().trim();
if (val) {
labels.push(val);
}
});
return labels;
}
/**
* Get current label count.
*
* @return {number} Number of labels.
*/
function getLabelCount() {
return getLabels().length;
}
/** /**
* Generate a random token. * Generate a random token.
* *
@@ -96,5 +594,23 @@
} }
return token; return token;
} }
});
/**
* Download a file.
*
* @param {string} content File content.
* @param {string} filename Filename.
* @param {string} type MIME type.
*/
function downloadFile(content, filename, type) {
var blob = new Blob([content], { type: type });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
})(jQuery); })(jQuery);

Binary file not shown.

View File

@@ -3,7 +3,7 @@
# This file is distributed under the GPL v2 or later. # This file is distributed under the GPL v2 or later.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WP Prometheus 0.1.0\n" "Project-Id-Version: WP Prometheus 0.3.0\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues\n" "Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues\n"
"POT-Creation-Date: 2026-02-02T00:00:00+00:00\n" "POT-Creation-Date: 2026-02-02T00:00:00+00:00\n"
"PO-Revision-Date: 2026-02-02T00:00:00+00:00\n" "PO-Revision-Date: 2026-02-02T00:00:00+00:00\n"
@@ -15,184 +15,605 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/Admin/Settings.php:40 #: src/Admin/Settings.php
msgid "Metrics Settings" msgid "Metrics Settings"
msgstr "Metriken-Einstellungen" msgstr "Metriken-Einstellungen"
#: src/Admin/Settings.php:41 #: src/Admin/Settings.php
msgid "Metrics" msgid "Metrics"
msgstr "Metriken" msgstr "Metriken"
#: src/Admin/Settings.php:58 #: src/Admin/Settings.php
msgid "License Settings" msgid "License"
msgstr "Lizenz-Einstellungen" msgstr "Lizenz"
#: src/Admin/Settings.php:65 #: src/Admin/Settings.php
msgid "Authentication" msgid "Help"
msgstr "Authentifizierung" msgstr "Hilfe"
#: src/Admin/Settings.php:73 #: src/Admin/Settings.php
msgid "Default Metrics" msgid "Custom Metrics"
msgstr "Standard-Metriken" msgstr "Eigene Metriken"
#: src/Admin/Settings.php:150 #: src/Admin/Settings.php
msgid "Dashboards"
msgstr "Dashboards"
#: src/Admin/Settings.php
msgid "License settings saved." msgid "License settings saved."
msgstr "Lizenz-Einstellungen gespeichert." msgstr "Lizenz-Einstellungen gespeichert."
#: src/Admin/Settings.php:195 #: src/Admin/Settings.php
msgid "License is active and valid." msgid "License is active and valid."
msgstr "Lizenz ist aktiv und gueltig." msgstr "Lizenz ist aktiv und gueltig."
#: src/Admin/Settings.php:196 #: src/Admin/Settings.php
msgid "License is invalid." msgid "License is invalid."
msgstr "Lizenz ist ungueltig." msgstr "Lizenz ist ungueltig."
#: src/Admin/Settings.php:197 #: src/Admin/Settings.php
msgid "License has expired." msgid "License has expired."
msgstr "Lizenz ist abgelaufen." msgstr "Lizenz ist abgelaufen."
#: src/Admin/Settings.php:198 #: src/Admin/Settings.php
msgid "License has been revoked." msgid "License has been revoked."
msgstr "Lizenz wurde widerrufen." msgstr "Lizenz wurde widerrufen."
#: src/Admin/Settings.php:199 #: src/Admin/Settings.php
msgid "License is inactive." msgid "License is inactive."
msgstr "Lizenz ist inaktiv." msgstr "Lizenz ist inaktiv."
#: src/Admin/Settings.php:200 #: src/Admin/Settings.php
msgid "License has not been validated yet." msgid "License has not been validated yet."
msgstr "Lizenz wurde noch nicht validiert." msgstr "Lizenz wurde noch nicht validiert."
#: src/Admin/Settings.php:201 #: src/Admin/Settings.php
msgid "License server is not configured." msgid "License server is not configured."
msgstr "Lizenz-Server ist nicht konfiguriert." msgstr "Lizenz-Server ist nicht konfiguriert."
#: src/Admin/Settings.php
msgid "Unknown status."
msgstr "Unbekannter Status."
#. translators: %s: Expiration date #. translators: %s: Expiration date
#: src/Admin/Settings.php:214 #: src/Admin/Settings.php
msgid "Expires: %s" msgid "Expires: %s"
msgstr "Laeuft ab: %s" msgstr "Laeuft ab: %s"
#. translators: %s: Time ago #. translators: %s: Time ago
#: src/Admin/Settings.php:225 #: src/Admin/Settings.php
msgid "Last checked: %s ago" msgid "Last checked: %s ago"
msgstr "Zuletzt geprueft: vor %s" msgstr "Zuletzt geprueft: vor %s"
#: src/Admin/Settings.php:239 #: src/Admin/Settings.php
msgid "License Server URL" msgid "License Server URL"
msgstr "Lizenz-Server URL" msgstr "Lizenz-Server URL"
#: src/Admin/Settings.php:249 #: src/Admin/Settings.php
msgid "License Key" msgid "License Key"
msgstr "Lizenzschluessel" msgstr "Lizenzschluessel"
#: src/Admin/Settings.php:259 #: src/Admin/Settings.php
msgid "Server Secret" msgid "Server Secret"
msgstr "Server-Geheimnis" msgstr "Server-Geheimnis"
#: src/Admin/Settings.php:264 #: src/Admin/Settings.php
msgid "Leave empty to keep existing." msgid "Leave empty to keep existing."
msgstr "Leer lassen, um bestehenden Wert zu behalten." msgstr "Leer lassen, um bestehenden Wert zu behalten."
#: src/Admin/Settings.php:270 #: src/Admin/Settings.php
msgid "Save License Settings" msgid "Save License Settings"
msgstr "Lizenz-Einstellungen speichern" msgstr "Lizenz-Einstellungen speichern"
#: src/Admin/Settings.php:272 #: src/Admin/Settings.php
msgid "Validate License" msgid "Validate License"
msgstr "Lizenz validieren" msgstr "Lizenz validieren"
#: src/Admin/Settings.php:275 #: src/Admin/Settings.php
msgid "Activate License" msgid "Activate License"
msgstr "Lizenz aktivieren" msgstr "Lizenz aktivieren"
#: src/Admin/Settings.php:301 #: src/Admin/Settings.php
msgid "Configure authentication for the /metrics endpoint." msgid "Authentication"
msgstr "Authentifizierung fuer den /metrics-Endpunkt konfigurieren." msgstr "Authentifizierung"
#: src/Admin/Settings.php:310 #: src/Admin/Settings.php
msgid "Select which default metrics to expose."
msgstr "Waehlen Sie, welche Standard-Metriken bereitgestellt werden sollen."
#: src/Admin/Settings.php:324
msgid "Regenerate"
msgstr "Neu generieren"
#: src/Admin/Settings.php:327
msgid "Use this token to authenticate Prometheus scrape requests."
msgstr "Verwenden Sie diesen Token zur Authentifizierung von Prometheus-Abfragen."
#: src/Admin/Settings.php:340
msgid "WordPress Info (version, PHP version, multisite)"
msgstr "WordPress-Info (Version, PHP-Version, Multisite)"
#: src/Admin/Settings.php:341
msgid "Total Users by Role"
msgstr "Benutzer nach Rolle"
#: src/Admin/Settings.php:342
msgid "Total Posts by Type and Status"
msgstr "Beitraege nach Typ und Status"
#: src/Admin/Settings.php:343
msgid "Total Comments by Status"
msgstr "Kommentare nach Status"
#: src/Admin/Settings.php:344
msgid "Total Plugins (active/inactive)"
msgstr "Plugins (aktiv/inaktiv)"
#: src/Admin/Settings.php:345
msgid "HTTP Requests Total (by method, status, endpoint)"
msgstr "HTTP-Anfragen gesamt (nach Methode, Status, Endpunkt)"
#: src/Admin/Settings.php:346
msgid "HTTP Request Duration (histogram)"
msgstr "HTTP-Anfragedauer (Histogramm)"
#: src/Admin/Settings.php:347
msgid "Database Queries Total (by endpoint)"
msgstr "Datenbank-Abfragen gesamt (nach Endpunkt)"
#: src/Admin/Settings.php:369
msgid "Prometheus Configuration"
msgstr "Prometheus-Konfiguration"
#: src/Admin/Settings.php:370
msgid "Add the following to your prometheus.yml:"
msgstr "Fuegen Sie Folgendes zu Ihrer prometheus.yml hinzu:"
#. translators: %s: Endpoint URL
#: src/Admin/Settings.php:385
msgid "Metrics endpoint: %s"
msgstr "Metriken-Endpunkt: %s"
#: src/Admin/Settings.php:93
msgid "Auth Token"
msgstr "Auth-Token"
#: src/Admin/Settings.php:101
msgid "Enabled Metrics" msgid "Enabled Metrics"
msgstr "Aktivierte Metriken" msgstr "Aktivierte Metriken"
#: src/Plugin.php:120 #: src/Admin/Settings.php
msgid "Configure authentication for the /metrics endpoint."
msgstr "Authentifizierung fuer den /metrics-Endpunkt konfigurieren."
#: src/Admin/Settings.php
msgid "Select which metrics to expose on the /metrics endpoint."
msgstr "Waehlen Sie, welche Metriken auf dem /metrics-Endpunkt bereitgestellt werden sollen."
#: src/Admin/Settings.php
msgid "Auth Token"
msgstr "Auth-Token"
#: src/Admin/Settings.php
msgid "Select Metrics"
msgstr "Metriken auswaehlen"
#: src/Admin/Settings.php
msgid "Regenerate"
msgstr "Neu generieren"
#: src/Admin/Settings.php
msgid "Use this token to authenticate Prometheus scrape requests."
msgstr "Verwenden Sie diesen Token zur Authentifizierung von Prometheus-Abfragen."
#: src/Admin/Settings.php
msgid "Static Metrics"
msgstr "Statische Metriken"
#: src/Admin/Settings.php
msgid "WordPress Info (version, PHP version, multisite)"
msgstr "WordPress-Info (Version, PHP-Version, Multisite)"
#: src/Admin/Settings.php
msgid "Total Users by Role"
msgstr "Benutzer nach Rolle"
#: src/Admin/Settings.php
msgid "Total Posts by Type and Status"
msgstr "Beitraege nach Typ und Status"
#: src/Admin/Settings.php
msgid "Total Comments by Status"
msgstr "Kommentare nach Status"
#: src/Admin/Settings.php
msgid "Total Plugins (active/inactive)"
msgstr "Plugins (aktiv/inaktiv)"
#: src/Admin/Settings.php
msgid "Cron Events (scheduled tasks, overdue, next run)"
msgstr "Cron-Ereignisse (geplante Aufgaben, ueberfaellig, naechste Ausfuehrung)"
#: src/Admin/Settings.php
msgid "Transients (total, expiring, expired)"
msgstr "Transienten (gesamt, ablaufend, abgelaufen)"
#: src/Admin/Settings.php
msgid "Runtime Metrics"
msgstr "Laufzeit-Metriken"
#: src/Admin/Settings.php
msgid "Runtime metrics track data across requests. Enable only what you need to minimize performance impact."
msgstr "Laufzeit-Metriken erfassen Daten ueber Anfragen hinweg. Aktivieren Sie nur, was Sie benoetigen, um Auswirkungen auf die Leistung zu minimieren."
#: src/Admin/Settings.php
msgid "HTTP Requests Total (by method, status, endpoint)"
msgstr "HTTP-Anfragen gesamt (nach Methode, Status, Endpunkt)"
#: src/Admin/Settings.php
msgid "HTTP Request Duration (histogram)"
msgstr "HTTP-Anfragedauer (Histogramm)"
#: src/Admin/Settings.php
msgid "Database Queries Total (by endpoint)"
msgstr "Datenbank-Abfragen gesamt (nach Endpunkt)"
#: src/Admin/Settings.php
msgid "WooCommerce Metrics"
msgstr "WooCommerce-Metriken"
#: src/Admin/Settings.php
msgid "Metrics specific to WooCommerce stores. Only available when WooCommerce is active."
msgstr "Metriken speziell fuer WooCommerce-Shops. Nur verfuegbar, wenn WooCommerce aktiv ist."
#: src/Admin/Settings.php
msgid "WooCommerce Products (by status and type)"
msgstr "WooCommerce-Produkte (nach Status und Typ)"
#: src/Admin/Settings.php
msgid "WooCommerce Orders (by status)"
msgstr "WooCommerce-Bestellungen (nach Status)"
#: src/Admin/Settings.php
msgid "WooCommerce Revenue (all time, today, month)"
msgstr "WooCommerce-Umsatz (gesamt, heute, Monat)"
#: src/Admin/Settings.php
msgid "WooCommerce Customers (registered, guest)"
msgstr "WooCommerce-Kunden (registriert, Gast)"
#: src/Admin/Settings.php
msgid "Reset Runtime Metrics"
msgstr "Laufzeit-Metriken zuruecksetzen"
#: src/Admin/Settings.php
msgid "Clear all accumulated runtime metric data."
msgstr "Alle gesammelten Laufzeit-Metrikdaten loeschen."
#: src/Admin/Settings.php
msgid "Reset Metrics"
msgstr "Metriken zuruecksetzen"
#: src/Admin/Settings.php
msgid "Prometheus Configuration"
msgstr "Prometheus-Konfiguration"
#: src/Admin/Settings.php
msgid "Add the following to your prometheus.yml:"
msgstr "Fuegen Sie Folgendes zu Ihrer prometheus.yml hinzu:"
#: src/Admin/Settings.php
msgid "Endpoint Information"
msgstr "Endpunkt-Informationen"
#: src/Admin/Settings.php
msgid "Metrics URL"
msgstr "Metriken-URL"
#: src/Admin/Settings.php
msgid "Testing the Endpoint"
msgstr "Endpunkt testen"
#: src/Admin/Settings.php
msgid "You can test the endpoint using curl:"
msgstr "Sie koennen den Endpunkt mit curl testen:"
#: src/Admin/Settings.php
msgid "Available Metrics"
msgstr "Verfuegbare Metriken"
#: src/Admin/Settings.php
msgid "Metric"
msgstr "Metrik"
#: src/Admin/Settings.php
msgid "Type"
msgstr "Typ"
#: src/Admin/Settings.php
msgid "Description"
msgstr "Beschreibung"
#: src/Admin/Settings.php
msgid "Gauge"
msgstr "Gauge"
#: src/Admin/Settings.php
msgid "Counter"
msgstr "Counter"
#: src/Admin/Settings.php
msgid "Histogram"
msgstr "Histogramm"
#: src/Admin/Settings.php
msgid "WordPress installation info"
msgstr "WordPress-Installationsinformationen"
#: src/Admin/Settings.php
msgid "Total users by role"
msgstr "Benutzer gesamt nach Rolle"
#: src/Admin/Settings.php
msgid "Total posts by type and status"
msgstr "Beitraege gesamt nach Typ und Status"
#: src/Admin/Settings.php
msgid "Total comments by status"
msgstr "Kommentare gesamt nach Status"
#: src/Admin/Settings.php
msgid "Total plugins by status"
msgstr "Plugins gesamt nach Status"
#: src/Admin/Settings.php
msgid "HTTP requests by method, status, endpoint"
msgstr "HTTP-Anfragen nach Methode, Status, Endpunkt"
#: src/Admin/Settings.php
msgid "HTTP request duration distribution"
msgstr "HTTP-Anfragedauer-Verteilung"
#: src/Admin/Settings.php
msgid "Database queries by endpoint"
msgstr "Datenbank-Abfragen nach Endpunkt"
#: src/Admin/Settings.php
msgid "Scheduled cron events by hook"
msgstr "Geplante Cron-Ereignisse nach Hook"
#: src/Admin/Settings.php
msgid "Number of overdue cron events"
msgstr "Anzahl ueberfaelliger Cron-Ereignisse"
#: src/Admin/Settings.php
msgid "Unix timestamp of next scheduled cron"
msgstr "Unix-Zeitstempel des naechsten geplanten Crons"
#: src/Admin/Settings.php
msgid "Total transients by type"
msgstr "Transienten gesamt nach Typ"
#: src/Admin/Settings.php
msgid "WooCommerce products by status and type"
msgstr "WooCommerce-Produkte nach Status und Typ"
#: src/Admin/Settings.php
msgid "WooCommerce orders by status"
msgstr "WooCommerce-Bestellungen nach Status"
#: src/Admin/Settings.php
msgid "WooCommerce revenue by period"
msgstr "WooCommerce-Umsatz nach Zeitraum"
#: src/Admin/Settings.php
msgid "WooCommerce customers by type"
msgstr "WooCommerce-Kunden nach Typ"
#: src/Admin/Settings.php
msgid "You can add custom metrics using the wp_prometheus_collect_metrics action:"
msgstr "Sie koennen benutzerdefinierte Metriken mit der wp_prometheus_collect_metrics-Aktion hinzufuegen:"
#: src/Admin/Settings.php
msgid "Add Custom Metric"
msgstr "Eigene Metrik hinzufuegen"
#: src/Admin/Settings.php
msgid "Edit Custom Metric"
msgstr "Eigene Metrik bearbeiten"
#: src/Admin/Settings.php
msgid "Metric Name"
msgstr "Metrik-Name"
#: src/Admin/Settings.php
msgid "Must follow Prometheus naming conventions."
msgstr "Muss den Prometheus-Namenskonventionen entsprechen."
#: src/Admin/Settings.php
msgid "Help Text"
msgstr "Hilfetext"
#: src/Admin/Settings.php
msgid "Description shown in Prometheus output."
msgstr "Beschreibung in der Prometheus-Ausgabe."
#: src/Admin/Settings.php
msgid "Value Type"
msgstr "Werttyp"
#: src/Admin/Settings.php
msgid "Static Value"
msgstr "Statischer Wert"
#: src/Admin/Settings.php
msgid "WordPress Option"
msgstr "WordPress-Option"
#: src/Admin/Settings.php
msgid "Static Value:"
msgstr "Statischer Wert:"
#: src/Admin/Settings.php
msgid "Option Name:"
msgstr "Optionsname:"
#: src/Admin/Settings.php
msgid "The name of the WordPress option to read."
msgstr "Der Name der zu lesenden WordPress-Option."
#: src/Admin/Settings.php
msgid "Labels"
msgstr "Labels"
#: src/Admin/Settings.php
msgid "Label name"
msgstr "Label-Name"
#: src/Admin/Settings.php
msgid "Add Label"
msgstr "Label hinzufuegen"
#: src/Admin/Settings.php
msgid "Label Values"
msgstr "Label-Werte"
#: src/Admin/Settings.php
msgid "Value"
msgstr "Wert"
#: src/Admin/Settings.php
msgid "Add Value Row"
msgstr "Wertezeile hinzufuegen"
#: src/Admin/Settings.php
msgid "Enabled"
msgstr "Aktiviert"
#: src/Admin/Settings.php
msgid "Save Metric"
msgstr "Metrik speichern"
#: src/Admin/Settings.php
msgid "Cancel"
msgstr "Abbrechen"
#: src/Admin/Settings.php
msgid "Your Custom Metrics"
msgstr "Ihre eigenen Metriken"
#: src/Admin/Settings.php
msgid "Name"
msgstr "Name"
#: src/Admin/Settings.php
msgid "Status"
msgstr "Status"
#: src/Admin/Settings.php
msgid "Actions"
msgstr "Aktionen"
#: src/Admin/Settings.php
msgid "Active"
msgstr "Aktiv"
#: src/Admin/Settings.php
msgid "Inactive"
msgstr "Inaktiv"
#: src/Admin/Settings.php
msgid "Edit"
msgstr "Bearbeiten"
#: src/Admin/Settings.php
msgid "Delete"
msgstr "Loeschen"
#: src/Admin/Settings.php
msgid "No custom metrics defined yet."
msgstr "Noch keine eigenen Metriken definiert."
#: src/Admin/Settings.php
msgid "Export / Import"
msgstr "Export / Import"
#: src/Admin/Settings.php
msgid "Export your custom metrics configuration for backup or transfer to another site."
msgstr "Exportieren Sie Ihre Metriken-Konfiguration zur Sicherung oder Uebertragung auf eine andere Website."
#: src/Admin/Settings.php
msgid "Export Metrics"
msgstr "Metriken exportieren"
#: src/Admin/Settings.php
msgid "Import Metrics"
msgstr "Metriken importieren"
#: src/Admin/Settings.php
msgid "Import Options"
msgstr "Import-Optionen"
#: src/Admin/Settings.php
msgid "Skip existing metrics"
msgstr "Bestehende Metriken ueberspringen"
#: src/Admin/Settings.php
msgid "Overwrite existing metrics"
msgstr "Bestehende Metriken ueberschreiben"
#: src/Admin/Settings.php
msgid "Rename duplicates"
msgstr "Duplikate umbenennen"
#: src/Admin/Settings.php
msgid "Import"
msgstr "Importieren"
#: src/Admin/Settings.php
msgid "Grafana Dashboard Templates"
msgstr "Grafana Dashboard-Vorlagen"
#: src/Admin/Settings.php
msgid "Download pre-built Grafana dashboards for visualizing your WordPress metrics."
msgstr "Laden Sie vorgefertigte Grafana-Dashboards zur Visualisierung Ihrer WordPress-Metriken herunter."
#: src/Admin/Settings.php
msgid "Download"
msgstr "Herunterladen"
#: src/Admin/Settings.php
msgid "Import instructions:"
msgstr "Import-Anleitung:"
#: src/Admin/Settings.php
msgid "Download the desired dashboard JSON file"
msgstr "Laden Sie die gewuenschte Dashboard-JSON-Datei herunter"
#: src/Admin/Settings.php
msgid "In Grafana, go to Dashboards > Import"
msgstr "Gehen Sie in Grafana zu Dashboards > Import"
#: src/Admin/Settings.php
msgid "Upload the JSON file or paste its contents"
msgstr "Laden Sie die JSON-Datei hoch oder fuegen Sie deren Inhalt ein"
#: src/Admin/Settings.php
msgid "Select your Prometheus data source"
msgstr "Waehlen Sie Ihre Prometheus-Datenquelle"
#: src/Admin/Settings.php
msgid "Click Import"
msgstr "Klicken Sie auf Import"
#: src/Metrics/CustomMetricBuilder.php
msgid "Metric name is required."
msgstr "Metrik-Name ist erforderlich."
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid metric name format."
msgstr "Ungueltiges Metrik-Namensformat."
#: src/Metrics/CustomMetricBuilder.php
msgid "A metric with this name already exists."
msgstr "Eine Metrik mit diesem Namen existiert bereits."
#: src/Metrics/CustomMetricBuilder.php
msgid "Help text is required."
msgstr "Hilfetext ist erforderlich."
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid value type."
msgstr "Ungueltiger Werttyp."
#: src/Metrics/CustomMetricBuilder.php
msgid "Static value must be numeric."
msgstr "Statischer Wert muss numerisch sein."
#: src/Metrics/CustomMetricBuilder.php
msgid "Option name is required for option value type."
msgstr "Optionsname ist fuer den Options-Werttyp erforderlich."
#. translators: %d: Maximum number of labels
#: src/Metrics/CustomMetricBuilder.php
msgid "Maximum %d labels allowed."
msgstr "Maximal %d Labels erlaubt."
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid label name format."
msgstr "Ungueltiges Label-Namensformat."
#. translators: %d: Maximum number of label value combinations
#: src/Metrics/CustomMetricBuilder.php
msgid "Maximum %d label value combinations allowed."
msgstr "Maximal %d Label-Wert-Kombinationen erlaubt."
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid JSON format."
msgstr "Ungueltiges JSON-Format."
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid export format."
msgstr "Ungueltiges Export-Format."
#: src/Plugin.php
msgid "Settings" msgid "Settings"
msgstr "Einstellungen" msgstr "Einstellungen"
#. translators: 1: Required PHP version, 2: Current PHP version #. translators: 1: Required PHP version, 2: Current PHP version
#: wp-prometheus.php:112 #: wp-prometheus.php
msgid "WP Prometheus requires PHP version %1$s or higher. You are running PHP %2$s." msgid "WP Prometheus requires PHP version %1$s or higher. You are running PHP %2$s."
msgstr "WP Prometheus erfordert PHP-Version %1$s oder hoeher. Sie verwenden PHP %2$s." msgstr "WP Prometheus erfordert PHP-Version %1$s oder hoeher. Sie verwenden PHP %2$s."
#. translators: 1: Required WordPress version, 2: Current WordPress version #. translators: 1: Required WordPress version, 2: Current WordPress version
#: wp-prometheus.php:127 #: wp-prometheus.php
msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s." msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s."
msgstr "WP Prometheus erfordert WordPress-Version %1$s oder hoeher. Sie verwenden WordPress %2$s." msgstr "WP Prometheus erfordert WordPress-Version %1$s oder hoeher. Sie verwenden WordPress %2$s."
#: wp-prometheus.php:140 #: wp-prometheus.php
msgid "WP Prometheus requires Composer dependencies to be installed. Please run \"composer install\" in the plugin directory." msgid "WP Prometheus requires Composer dependencies to be installed. Please run \"composer install\" in the plugin directory."
msgstr "WP Prometheus erfordert installierte Composer-Abhaengigkeiten. Bitte fuehren Sie \"composer install\" im Plugin-Verzeichnis aus." msgstr "WP Prometheus erfordert installierte Composer-Abhaengigkeiten. Bitte fuehren Sie \"composer install\" im Plugin-Verzeichnis aus."
#. translators: %s: Required PHP version #. translators: %s: Required PHP version
#: wp-prometheus.php:156 #: wp-prometheus.php
msgid "WP Prometheus requires PHP version %s or higher." msgid "WP Prometheus requires PHP version %s or higher."
msgstr "WP Prometheus erfordert PHP-Version %s oder hoeher." msgstr "WP Prometheus erfordert PHP-Version %s oder hoeher."

View File

@@ -2,7 +2,7 @@
# This file is distributed under the GPL v2 or later. # This file is distributed under the GPL v2 or later.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WP Prometheus 0.1.0\n" "Project-Id-Version: WP Prometheus 0.3.0\n"
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues\n" "Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues\n"
"POT-Creation-Date: 2026-02-02T00:00:00+00:00\n" "POT-Creation-Date: 2026-02-02T00:00:00+00:00\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -12,184 +12,605 @@ msgstr ""
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
#: src/Admin/Settings.php:40 #: src/Admin/Settings.php
msgid "Metrics Settings" msgid "Metrics Settings"
msgstr "" msgstr ""
#: src/Admin/Settings.php:41 #: src/Admin/Settings.php
msgid "Metrics" msgid "Metrics"
msgstr "" msgstr ""
#: src/Admin/Settings.php:58 #: src/Admin/Settings.php
msgid "License Settings" msgid "License"
msgstr "" msgstr ""
#: src/Admin/Settings.php:65 #: src/Admin/Settings.php
msgid "Authentication" msgid "Help"
msgstr "" msgstr ""
#: src/Admin/Settings.php:73 #: src/Admin/Settings.php
msgid "Default Metrics" msgid "Custom Metrics"
msgstr "" msgstr ""
#: src/Admin/Settings.php:150 #: src/Admin/Settings.php
msgid "Dashboards"
msgstr ""
#: src/Admin/Settings.php
msgid "License settings saved." msgid "License settings saved."
msgstr "" msgstr ""
#: src/Admin/Settings.php:195 #: src/Admin/Settings.php
msgid "License is active and valid." msgid "License is active and valid."
msgstr "" msgstr ""
#: src/Admin/Settings.php:196 #: src/Admin/Settings.php
msgid "License is invalid." msgid "License is invalid."
msgstr "" msgstr ""
#: src/Admin/Settings.php:197 #: src/Admin/Settings.php
msgid "License has expired." msgid "License has expired."
msgstr "" msgstr ""
#: src/Admin/Settings.php:198 #: src/Admin/Settings.php
msgid "License has been revoked." msgid "License has been revoked."
msgstr "" msgstr ""
#: src/Admin/Settings.php:199 #: src/Admin/Settings.php
msgid "License is inactive." msgid "License is inactive."
msgstr "" msgstr ""
#: src/Admin/Settings.php:200 #: src/Admin/Settings.php
msgid "License has not been validated yet." msgid "License has not been validated yet."
msgstr "" msgstr ""
#: src/Admin/Settings.php:201 #: src/Admin/Settings.php
msgid "License server is not configured." msgid "License server is not configured."
msgstr "" msgstr ""
#: src/Admin/Settings.php
msgid "Unknown status."
msgstr ""
#. translators: %s: Expiration date #. translators: %s: Expiration date
#: src/Admin/Settings.php:214 #: src/Admin/Settings.php
msgid "Expires: %s" msgid "Expires: %s"
msgstr "" msgstr ""
#. translators: %s: Time ago #. translators: %s: Time ago
#: src/Admin/Settings.php:225 #: src/Admin/Settings.php
msgid "Last checked: %s ago" msgid "Last checked: %s ago"
msgstr "" msgstr ""
#: src/Admin/Settings.php:239 #: src/Admin/Settings.php
msgid "License Server URL" msgid "License Server URL"
msgstr "" msgstr ""
#: src/Admin/Settings.php:249 #: src/Admin/Settings.php
msgid "License Key" msgid "License Key"
msgstr "" msgstr ""
#: src/Admin/Settings.php:259 #: src/Admin/Settings.php
msgid "Server Secret" msgid "Server Secret"
msgstr "" msgstr ""
#: src/Admin/Settings.php:264 #: src/Admin/Settings.php
msgid "Leave empty to keep existing." msgid "Leave empty to keep existing."
msgstr "" msgstr ""
#: src/Admin/Settings.php:270 #: src/Admin/Settings.php
msgid "Save License Settings" msgid "Save License Settings"
msgstr "" msgstr ""
#: src/Admin/Settings.php:272 #: src/Admin/Settings.php
msgid "Validate License" msgid "Validate License"
msgstr "" msgstr ""
#: src/Admin/Settings.php:275 #: src/Admin/Settings.php
msgid "Activate License" msgid "Activate License"
msgstr "" msgstr ""
#: src/Admin/Settings.php:301 #: src/Admin/Settings.php
msgid "Configure authentication for the /metrics endpoint." msgid "Authentication"
msgstr "" msgstr ""
#: src/Admin/Settings.php:310 #: src/Admin/Settings.php
msgid "Select which default metrics to expose."
msgstr ""
#: src/Admin/Settings.php:324
msgid "Regenerate"
msgstr ""
#: src/Admin/Settings.php:327
msgid "Use this token to authenticate Prometheus scrape requests."
msgstr ""
#: src/Admin/Settings.php:340
msgid "WordPress Info (version, PHP version, multisite)"
msgstr ""
#: src/Admin/Settings.php:341
msgid "Total Users by Role"
msgstr ""
#: src/Admin/Settings.php:342
msgid "Total Posts by Type and Status"
msgstr ""
#: src/Admin/Settings.php:343
msgid "Total Comments by Status"
msgstr ""
#: src/Admin/Settings.php:344
msgid "Total Plugins (active/inactive)"
msgstr ""
#: src/Admin/Settings.php:345
msgid "HTTP Requests Total (by method, status, endpoint)"
msgstr ""
#: src/Admin/Settings.php:346
msgid "HTTP Request Duration (histogram)"
msgstr ""
#: src/Admin/Settings.php:347
msgid "Database Queries Total (by endpoint)"
msgstr ""
#: src/Admin/Settings.php:369
msgid "Prometheus Configuration"
msgstr ""
#: src/Admin/Settings.php:370
msgid "Add the following to your prometheus.yml:"
msgstr ""
#. translators: %s: Endpoint URL
#: src/Admin/Settings.php:385
msgid "Metrics endpoint: %s"
msgstr ""
#: src/Admin/Settings.php:93
msgid "Auth Token"
msgstr ""
#: src/Admin/Settings.php:101
msgid "Enabled Metrics" msgid "Enabled Metrics"
msgstr "" msgstr ""
#: src/Plugin.php:120 #: src/Admin/Settings.php
msgid "Configure authentication for the /metrics endpoint."
msgstr ""
#: src/Admin/Settings.php
msgid "Select which metrics to expose on the /metrics endpoint."
msgstr ""
#: src/Admin/Settings.php
msgid "Auth Token"
msgstr ""
#: src/Admin/Settings.php
msgid "Select Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Regenerate"
msgstr ""
#: src/Admin/Settings.php
msgid "Use this token to authenticate Prometheus scrape requests."
msgstr ""
#: src/Admin/Settings.php
msgid "Static Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "WordPress Info (version, PHP version, multisite)"
msgstr ""
#: src/Admin/Settings.php
msgid "Total Users by Role"
msgstr ""
#: src/Admin/Settings.php
msgid "Total Posts by Type and Status"
msgstr ""
#: src/Admin/Settings.php
msgid "Total Comments by Status"
msgstr ""
#: src/Admin/Settings.php
msgid "Total Plugins (active/inactive)"
msgstr ""
#: src/Admin/Settings.php
msgid "Cron Events (scheduled tasks, overdue, next run)"
msgstr ""
#: src/Admin/Settings.php
msgid "Transients (total, expiring, expired)"
msgstr ""
#: src/Admin/Settings.php
msgid "Runtime Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Runtime metrics track data across requests. Enable only what you need to minimize performance impact."
msgstr ""
#: src/Admin/Settings.php
msgid "HTTP Requests Total (by method, status, endpoint)"
msgstr ""
#: src/Admin/Settings.php
msgid "HTTP Request Duration (histogram)"
msgstr ""
#: src/Admin/Settings.php
msgid "Database Queries Total (by endpoint)"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Metrics specific to WooCommerce stores. Only available when WooCommerce is active."
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce Products (by status and type)"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce Orders (by status)"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce Revenue (all time, today, month)"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce Customers (registered, guest)"
msgstr ""
#: src/Admin/Settings.php
msgid "Reset Runtime Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Clear all accumulated runtime metric data."
msgstr ""
#: src/Admin/Settings.php
msgid "Reset Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Prometheus Configuration"
msgstr ""
#: src/Admin/Settings.php
msgid "Add the following to your prometheus.yml:"
msgstr ""
#: src/Admin/Settings.php
msgid "Endpoint Information"
msgstr ""
#: src/Admin/Settings.php
msgid "Metrics URL"
msgstr ""
#: src/Admin/Settings.php
msgid "Testing the Endpoint"
msgstr ""
#: src/Admin/Settings.php
msgid "You can test the endpoint using curl:"
msgstr ""
#: src/Admin/Settings.php
msgid "Available Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Metric"
msgstr ""
#: src/Admin/Settings.php
msgid "Type"
msgstr ""
#: src/Admin/Settings.php
msgid "Description"
msgstr ""
#: src/Admin/Settings.php
msgid "Gauge"
msgstr ""
#: src/Admin/Settings.php
msgid "Counter"
msgstr ""
#: src/Admin/Settings.php
msgid "Histogram"
msgstr ""
#: src/Admin/Settings.php
msgid "WordPress installation info"
msgstr ""
#: src/Admin/Settings.php
msgid "Total users by role"
msgstr ""
#: src/Admin/Settings.php
msgid "Total posts by type and status"
msgstr ""
#: src/Admin/Settings.php
msgid "Total comments by status"
msgstr ""
#: src/Admin/Settings.php
msgid "Total plugins by status"
msgstr ""
#: src/Admin/Settings.php
msgid "HTTP requests by method, status, endpoint"
msgstr ""
#: src/Admin/Settings.php
msgid "HTTP request duration distribution"
msgstr ""
#: src/Admin/Settings.php
msgid "Database queries by endpoint"
msgstr ""
#: src/Admin/Settings.php
msgid "Scheduled cron events by hook"
msgstr ""
#: src/Admin/Settings.php
msgid "Number of overdue cron events"
msgstr ""
#: src/Admin/Settings.php
msgid "Unix timestamp of next scheduled cron"
msgstr ""
#: src/Admin/Settings.php
msgid "Total transients by type"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce products by status and type"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce orders by status"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce revenue by period"
msgstr ""
#: src/Admin/Settings.php
msgid "WooCommerce customers by type"
msgstr ""
#: src/Admin/Settings.php
msgid "You can add custom metrics using the wp_prometheus_collect_metrics action:"
msgstr ""
#: src/Admin/Settings.php
msgid "Add Custom Metric"
msgstr ""
#: src/Admin/Settings.php
msgid "Edit Custom Metric"
msgstr ""
#: src/Admin/Settings.php
msgid "Metric Name"
msgstr ""
#: src/Admin/Settings.php
msgid "Must follow Prometheus naming conventions."
msgstr ""
#: src/Admin/Settings.php
msgid "Help Text"
msgstr ""
#: src/Admin/Settings.php
msgid "Description shown in Prometheus output."
msgstr ""
#: src/Admin/Settings.php
msgid "Value Type"
msgstr ""
#: src/Admin/Settings.php
msgid "Static Value"
msgstr ""
#: src/Admin/Settings.php
msgid "WordPress Option"
msgstr ""
#: src/Admin/Settings.php
msgid "Static Value:"
msgstr ""
#: src/Admin/Settings.php
msgid "Option Name:"
msgstr ""
#: src/Admin/Settings.php
msgid "The name of the WordPress option to read."
msgstr ""
#: src/Admin/Settings.php
msgid "Labels"
msgstr ""
#: src/Admin/Settings.php
msgid "Label name"
msgstr ""
#: src/Admin/Settings.php
msgid "Add Label"
msgstr ""
#: src/Admin/Settings.php
msgid "Label Values"
msgstr ""
#: src/Admin/Settings.php
msgid "Value"
msgstr ""
#: src/Admin/Settings.php
msgid "Add Value Row"
msgstr ""
#: src/Admin/Settings.php
msgid "Enabled"
msgstr ""
#: src/Admin/Settings.php
msgid "Save Metric"
msgstr ""
#: src/Admin/Settings.php
msgid "Cancel"
msgstr ""
#: src/Admin/Settings.php
msgid "Your Custom Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Name"
msgstr ""
#: src/Admin/Settings.php
msgid "Status"
msgstr ""
#: src/Admin/Settings.php
msgid "Actions"
msgstr ""
#: src/Admin/Settings.php
msgid "Active"
msgstr ""
#: src/Admin/Settings.php
msgid "Inactive"
msgstr ""
#: src/Admin/Settings.php
msgid "Edit"
msgstr ""
#: src/Admin/Settings.php
msgid "Delete"
msgstr ""
#: src/Admin/Settings.php
msgid "No custom metrics defined yet."
msgstr ""
#: src/Admin/Settings.php
msgid "Export / Import"
msgstr ""
#: src/Admin/Settings.php
msgid "Export your custom metrics configuration for backup or transfer to another site."
msgstr ""
#: src/Admin/Settings.php
msgid "Export Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Import Metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Import Options"
msgstr ""
#: src/Admin/Settings.php
msgid "Skip existing metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Overwrite existing metrics"
msgstr ""
#: src/Admin/Settings.php
msgid "Rename duplicates"
msgstr ""
#: src/Admin/Settings.php
msgid "Import"
msgstr ""
#: src/Admin/Settings.php
msgid "Grafana Dashboard Templates"
msgstr ""
#: src/Admin/Settings.php
msgid "Download pre-built Grafana dashboards for visualizing your WordPress metrics."
msgstr ""
#: src/Admin/Settings.php
msgid "Download"
msgstr ""
#: src/Admin/Settings.php
msgid "Import instructions:"
msgstr ""
#: src/Admin/Settings.php
msgid "Download the desired dashboard JSON file"
msgstr ""
#: src/Admin/Settings.php
msgid "In Grafana, go to Dashboards > Import"
msgstr ""
#: src/Admin/Settings.php
msgid "Upload the JSON file or paste its contents"
msgstr ""
#: src/Admin/Settings.php
msgid "Select your Prometheus data source"
msgstr ""
#: src/Admin/Settings.php
msgid "Click Import"
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Metric name is required."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid metric name format."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "A metric with this name already exists."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Help text is required."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid value type."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Static value must be numeric."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Option name is required for option value type."
msgstr ""
#. translators: %d: Maximum number of labels
#: src/Metrics/CustomMetricBuilder.php
msgid "Maximum %d labels allowed."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid label name format."
msgstr ""
#. translators: %d: Maximum number of label value combinations
#: src/Metrics/CustomMetricBuilder.php
msgid "Maximum %d label value combinations allowed."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid JSON format."
msgstr ""
#: src/Metrics/CustomMetricBuilder.php
msgid "Invalid export format."
msgstr ""
#: src/Plugin.php
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#. translators: 1: Required PHP version, 2: Current PHP version #. translators: 1: Required PHP version, 2: Current PHP version
#: wp-prometheus.php:112 #: wp-prometheus.php
msgid "WP Prometheus requires PHP version %1$s or higher. You are running PHP %2$s." msgid "WP Prometheus requires PHP version %1$s or higher. You are running PHP %2$s."
msgstr "" msgstr ""
#. translators: 1: Required WordPress version, 2: Current WordPress version #. translators: 1: Required WordPress version, 2: Current WordPress version
#: wp-prometheus.php:127 #: wp-prometheus.php
msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s." msgid "WP Prometheus requires WordPress version %1$s or higher. You are running WordPress %2$s."
msgstr "" msgstr ""
#: wp-prometheus.php:140 #: wp-prometheus.php
msgid "WP Prometheus requires Composer dependencies to be installed. Please run \"composer install\" in the plugin directory." msgid "WP Prometheus requires Composer dependencies to be installed. Please run \"composer install\" in the plugin directory."
msgstr "" msgstr ""
#. translators: %s: Required PHP version #. translators: %s: Required PHP version
#: wp-prometheus.php:156 #: wp-prometheus.php
msgid "WP Prometheus requires PHP version %s or higher." msgid "WP Prometheus requires PHP version %s or higher."
msgstr "" msgstr ""

View File

@@ -0,0 +1,151 @@
<?php
/**
* Dashboard provider class.
*
* @package WP_Prometheus
*/
namespace Magdev\WpPrometheus\Admin;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* DashboardProvider class.
*
* Provides Grafana dashboard templates for download.
*/
class DashboardProvider {
/**
* Dashboard directory path.
*
* @var string
*/
private string $dashboard_dir;
/**
* Available dashboard definitions.
*
* @var array
*/
private array $dashboards = array();
/**
* Constructor.
*/
public function __construct() {
$this->dashboard_dir = WP_PROMETHEUS_PATH . 'assets/dashboards/';
$this->dashboards = array(
'wordpress-overview' => array(
'title' => __( 'WordPress Overview', 'wp-prometheus' ),
'description' => __( 'General WordPress metrics including users, posts, comments, and plugins.', 'wp-prometheus' ),
'file' => 'wordpress-overview.json',
'icon' => 'dashicons-wordpress',
),
'wordpress-runtime' => array(
'title' => __( 'Runtime Performance', 'wp-prometheus' ),
'description' => __( 'HTTP request metrics, database query performance, and response times.', 'wp-prometheus' ),
'file' => 'wordpress-runtime.json',
'icon' => 'dashicons-performance',
),
'wordpress-woocommerce' => array(
'title' => __( 'WooCommerce Store', 'wp-prometheus' ),
'description' => __( 'WooCommerce metrics including products, orders, revenue, and customers.', 'wp-prometheus' ),
'file' => 'wordpress-woocommerce.json',
'icon' => 'dashicons-cart',
),
);
}
/**
* Get list of available dashboards.
*
* @return array
*/
public function get_available(): array {
$available = array();
foreach ( $this->dashboards as $slug => $dashboard ) {
$file_path = $this->dashboard_dir . $dashboard['file'];
if ( file_exists( $file_path ) ) {
$available[ $slug ] = $dashboard;
}
}
return $available;
}
/**
* Get dashboard content by slug.
*
* @param string $slug Dashboard slug.
* @return string|null JSON content or null if not found.
*/
public function get_dashboard( string $slug ): ?string {
// Validate slug to prevent directory traversal.
$slug = sanitize_file_name( $slug );
if ( ! isset( $this->dashboards[ $slug ] ) ) {
return null;
}
$file_path = $this->dashboard_dir . $this->dashboards[ $slug ]['file'];
// Security: Ensure file is within dashboard directory.
$real_path = realpath( $file_path );
$real_dir = realpath( $this->dashboard_dir );
if ( false === $real_path || false === $real_dir || strpos( $real_path, $real_dir ) !== 0 ) {
return null;
}
if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
return null;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$content = file_get_contents( $file_path );
if ( false === $content ) {
return null;
}
return $content;
}
/**
* Get dashboard metadata by slug.
*
* @param string $slug Dashboard slug.
* @return array|null Dashboard metadata or null if not found.
*/
public function get_metadata( string $slug ): ?array {
$slug = sanitize_file_name( $slug );
if ( ! isset( $this->dashboards[ $slug ] ) ) {
return null;
}
return $this->dashboards[ $slug ];
}
/**
* Get filename for download.
*
* @param string $slug Dashboard slug.
* @return string|null Filename or null if not found.
*/
public function get_filename( string $slug ): ?string {
$slug = sanitize_file_name( $slug );
if ( ! isset( $this->dashboards[ $slug ] ) ) {
return null;
}
return $this->dashboards[ $slug ]['file'];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -64,6 +64,7 @@ final class Installer {
'wp_prometheus_enable_default_metrics', 'wp_prometheus_enable_default_metrics',
'wp_prometheus_enabled_metrics', 'wp_prometheus_enabled_metrics',
'wp_prometheus_runtime_metrics', 'wp_prometheus_runtime_metrics',
'wp_prometheus_custom_metrics',
); );
foreach ( $options as $option ) { foreach ( $options as $option ) {

View File

@@ -302,10 +302,47 @@ final class Manager {
* @return bool * @return bool
*/ */
public static function is_license_valid(): bool { public static function is_license_valid(): bool {
// Bypass license check on localhost for development.
if ( self::is_localhost() ) {
return true;
}
$status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' ); $status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
return 'valid' === $status; return 'valid' === $status;
} }
/**
* Check if the current site is running on localhost.
*
* @return bool
*/
public static function is_localhost(): bool {
$host = wp_parse_url( home_url(), PHP_URL_HOST );
$localhost_patterns = array(
'localhost',
'127.0.0.1',
'::1',
);
// Check exact matches.
if ( in_array( $host, $localhost_patterns, true ) ) {
return true;
}
// Check .localhost TLD (e.g., mysite.localhost).
if ( str_ends_with( $host, '.localhost' ) ) {
return true;
}
// Check .local TLD (common for local development).
if ( str_ends_with( $host, '.local' ) ) {
return true;
}
return false;
}
/** /**
* Get the license key. * Get the license key.
* *
@@ -396,9 +433,16 @@ final class Manager {
* @return void * @return void
*/ */
private function update_cached_status( string $status, array $data = array() ): void { private function update_cached_status( string $status, array $data = array() ): void {
$previous_status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
update_option( self::OPTION_LICENSE_STATUS, $status ); update_option( self::OPTION_LICENSE_STATUS, $status );
update_option( self::OPTION_LICENSE_DATA, $data ); update_option( self::OPTION_LICENSE_DATA, $data );
update_option( self::OPTION_LAST_CHECK, time() ); update_option( self::OPTION_LAST_CHECK, time() );
// Flush rewrite rules when license becomes valid to register the /metrics endpoint.
if ( 'valid' === $status && 'valid' !== $previous_status ) {
flush_rewrite_rules();
}
} }
/** /**
@@ -465,6 +509,9 @@ final class Manager {
update_option( self::OPTION_LICENSE_DATA, array() ); update_option( self::OPTION_LICENSE_DATA, array() );
delete_transient( self::TRANSIENT_LICENSE_CHECK ); delete_transient( self::TRANSIENT_LICENSE_CHECK );
// Flush rewrite rules to remove the /metrics endpoint.
flush_rewrite_rules();
wp_send_json_success( array( wp_send_json_success( array(
'success' => true, 'success' => true,
'message' => __( 'License deactivated.', 'wp-prometheus' ), 'message' => __( 'License deactivated.', 'wp-prometheus' ),

View File

@@ -10,6 +10,7 @@ namespace Magdev\WpPrometheus\Metrics;
use Prometheus\CollectorRegistry; use Prometheus\CollectorRegistry;
use Prometheus\Storage\InMemory; use Prometheus\Storage\InMemory;
use Prometheus\RenderTextFormat; use Prometheus\RenderTextFormat;
use Magdev\WpPrometheus\Metrics\CustomMetricBuilder;
// Prevent direct file access. // Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@@ -95,9 +96,28 @@ class Collector {
$this->collect_plugins_total(); $this->collect_plugins_total();
} }
// Collect cron metrics.
if ( in_array( 'wordpress_cron_events_total', $enabled_metrics, true ) ) {
$this->collect_cron_metrics();
}
// Collect transient metrics.
if ( in_array( 'wordpress_transients_total', $enabled_metrics, true ) ) {
$this->collect_transient_metrics();
}
// Collect WooCommerce metrics (if WooCommerce is active).
if ( $this->is_woocommerce_active() ) {
$this->collect_woocommerce_metrics( $enabled_metrics );
}
// Collect runtime metrics (HTTP requests, DB queries). // Collect runtime metrics (HTTP requests, DB queries).
$this->collect_runtime_metrics( $enabled_metrics ); $this->collect_runtime_metrics( $enabled_metrics );
// Collect custom user-defined metrics.
$custom_builder = new CustomMetricBuilder();
$custom_builder->register_with_collector( $this );
/** /**
* Fires after default metrics are collected. * Fires after default metrics are collected.
* *
@@ -236,6 +256,375 @@ class Collector {
$gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) ); $gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) );
} }
/**
* Collect cron metrics.
*
* @return void
*/
private function collect_cron_metrics(): void {
$cron_array = _get_cron_array();
if ( ! is_array( $cron_array ) ) {
return;
}
// Events total gauge.
$events_gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'cron_events_total',
'Total number of scheduled cron events',
array( 'hook' )
);
// Count events by hook.
$hook_counts = array();
$total_events = 0;
$overdue_count = 0;
$current_time = time();
$next_run = PHP_INT_MAX;
foreach ( $cron_array as $timestamp => $cron ) {
if ( $timestamp < $next_run ) {
$next_run = $timestamp;
}
foreach ( $cron as $hook => $events ) {
$event_count = count( $events );
$total_events += $event_count;
if ( ! isset( $hook_counts[ $hook ] ) ) {
$hook_counts[ $hook ] = 0;
}
$hook_counts[ $hook ] += $event_count;
// Check if overdue.
if ( $timestamp < $current_time ) {
$overdue_count += $event_count;
}
}
}
// Set events by hook (limit to top 20 to avoid cardinality explosion).
arsort( $hook_counts );
$hook_counts = array_slice( $hook_counts, 0, 20, true );
foreach ( $hook_counts as $hook => $count ) {
$events_gauge->set( $count, array( $hook ) );
}
// Overdue events gauge.
$overdue_gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'cron_overdue_total',
'Number of overdue cron events',
array()
);
$overdue_gauge->set( $overdue_count, array() );
// Next run timestamp.
$next_run_gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'cron_next_run_timestamp',
'Unix timestamp of next scheduled cron event',
array()
);
if ( $next_run !== PHP_INT_MAX ) {
$next_run_gauge->set( $next_run, array() );
}
}
/**
* Collect transient metrics.
*
* @return void
*/
private function collect_transient_metrics(): void {
global $wpdb;
// Count all transients.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$transient_count = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '_transient_%' AND option_name NOT LIKE '_transient_timeout_%'"
);
// Count transients with expiration.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$expiring_count = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_%'"
);
// Count expired transients.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$expired_count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_%%' AND option_value < %d",
time()
)
);
// Transients total gauge.
$transients_gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'transients_total',
'Total number of transients in database',
array( 'type' )
);
$transients_gauge->set( (int) $transient_count, array( 'total' ) );
$transients_gauge->set( (int) $expiring_count, array( 'with_expiration' ) );
$transients_gauge->set( (int) $transient_count - (int) $expiring_count, array( 'persistent' ) );
$transients_gauge->set( (int) $expired_count, array( 'expired' ) );
// Site transients (for multisite).
if ( is_multisite() ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$site_transient_count = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->sitemeta} WHERE meta_key LIKE '_site_transient_%' AND meta_key NOT LIKE '_site_transient_timeout_%'"
);
$transients_gauge->set( (int) $site_transient_count, array( 'site_transients' ) );
}
}
/**
* Check if WooCommerce is active.
*
* @return bool
*/
private function is_woocommerce_active(): bool {
return class_exists( 'WooCommerce' );
}
/**
* Collect WooCommerce metrics.
*
* @param array $enabled_metrics List of enabled metrics.
* @return void
*/
private function collect_woocommerce_metrics( array $enabled_metrics ): void {
// Products total.
if ( in_array( 'wordpress_woocommerce_products_total', $enabled_metrics, true ) ) {
$this->collect_woocommerce_products();
}
// Orders total.
if ( in_array( 'wordpress_woocommerce_orders_total', $enabled_metrics, true ) ) {
$this->collect_woocommerce_orders();
}
// Revenue.
if ( in_array( 'wordpress_woocommerce_revenue_total', $enabled_metrics, true ) ) {
$this->collect_woocommerce_revenue();
}
// Customers.
if ( in_array( 'wordpress_woocommerce_customers_total', $enabled_metrics, true ) ) {
$this->collect_woocommerce_customers();
}
}
/**
* Collect WooCommerce products metrics.
*
* @return void
*/
private function collect_woocommerce_products(): void {
$gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'woocommerce_products_total',
'Total number of WooCommerce products by status and type',
array( 'status', 'type' )
);
// Get product counts by status.
$product_counts = wp_count_posts( 'product' );
$product_types = wc_get_product_types();
foreach ( get_object_vars( $product_counts ) as $status => $count ) {
if ( (int) $count > 0 ) {
$gauge->set( (int) $count, array( $status, 'all' ) );
}
}
// Count by product type (for published products only).
foreach ( array_keys( $product_types ) as $type ) {
$args = array(
'status' => 'publish',
'type' => $type,
'limit' => -1,
'return' => 'ids',
);
$products = wc_get_products( $args );
$gauge->set( count( $products ), array( 'publish', $type ) );
}
}
/**
* Collect WooCommerce orders metrics.
*
* @return void
*/
private function collect_woocommerce_orders(): void {
$gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'woocommerce_orders_total',
'Total number of WooCommerce orders by status',
array( 'status' )
);
// Get all registered order statuses and count each.
$statuses = wc_get_order_statuses();
foreach ( array_keys( $statuses ) as $status ) {
// Remove 'wc-' prefix for the label.
$status_label = str_replace( 'wc-', '', $status );
$count = wc_orders_count( $status );
$gauge->set( (int) $count, array( $status_label ) );
}
}
/**
* Collect WooCommerce revenue metrics.
*
* @return void
*/
private function collect_woocommerce_revenue(): void {
global $wpdb;
$gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'woocommerce_revenue_total',
'Total WooCommerce revenue',
array( 'period', 'currency' )
);
$currency = get_woocommerce_currency();
// Check if HPOS (High-Performance Order Storage) is enabled.
$hpos_enabled = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
&& \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
if ( $hpos_enabled ) {
$orders_table = $wpdb->prefix . 'wc_orders';
// Total revenue (all time) - completed and processing orders.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$total_revenue = $wpdb->get_var(
"SELECT SUM(total_amount) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing')"
);
// Today's revenue.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$today_revenue = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUM(total_amount) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND DATE(date_created_gmt) = %s",
gmdate( 'Y-m-d' )
)
);
// This month's revenue.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$month_revenue = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUM(total_amount) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND YEAR(date_created_gmt) = %d AND MONTH(date_created_gmt) = %d",
gmdate( 'Y' ),
gmdate( 'm' )
)
);
} else {
// Legacy post-based orders.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$total_revenue = $wpdb->get_var(
"SELECT SUM(meta_value) FROM {$wpdb->postmeta} pm
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '_order_total'
AND p.post_type = 'shop_order'
AND p.post_status IN ('wc-completed', 'wc-processing')"
);
// Today's revenue.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$today_revenue = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUM(meta_value) FROM {$wpdb->postmeta} pm
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '_order_total'
AND p.post_type = 'shop_order'
AND p.post_status IN ('wc-completed', 'wc-processing')
AND DATE(p.post_date_gmt) = %s",
gmdate( 'Y-m-d' )
)
);
// This month's revenue.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$month_revenue = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUM(meta_value) FROM {$wpdb->postmeta} pm
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '_order_total'
AND p.post_type = 'shop_order'
AND p.post_status IN ('wc-completed', 'wc-processing')
AND YEAR(p.post_date_gmt) = %d
AND MONTH(p.post_date_gmt) = %d",
gmdate( 'Y' ),
gmdate( 'm' )
)
);
}
$gauge->set( (float) ( $total_revenue ?? 0 ), array( 'all_time', $currency ) );
$gauge->set( (float) ( $today_revenue ?? 0 ), array( 'today', $currency ) );
$gauge->set( (float) ( $month_revenue ?? 0 ), array( 'month', $currency ) );
}
/**
* Collect WooCommerce customers metrics.
*
* @return void
*/
private function collect_woocommerce_customers(): void {
$gauge = $this->registry->getOrRegisterGauge(
$this->namespace,
'woocommerce_customers_total',
'Total number of WooCommerce customers',
array( 'type' )
);
// Count users with customer role.
$customer_count = count_users();
$customers = $customer_count['avail_roles']['customer'] ?? 0;
$gauge->set( $customers, array( 'registered' ) );
// Count guest orders (orders without user_id).
global $wpdb;
// Check if HPOS is enabled.
$hpos_enabled = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
&& \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
if ( $hpos_enabled ) {
$orders_table = $wpdb->prefix . 'wc_orders';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$guest_orders = $wpdb->get_var(
"SELECT COUNT(DISTINCT billing_email) FROM {$orders_table} WHERE customer_id = 0 AND billing_email != ''"
);
} else {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$guest_orders = $wpdb->get_var(
"SELECT COUNT(DISTINCT pm.meta_value) FROM {$wpdb->postmeta} pm
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
LEFT JOIN {$wpdb->postmeta} pm2 ON pm2.post_id = p.ID AND pm2.meta_key = '_customer_user'
WHERE pm.meta_key = '_billing_email'
AND p.post_type = 'shop_order'
AND (pm2.meta_value = '0' OR pm2.meta_value IS NULL)"
);
}
$gauge->set( (int) $guest_orders, array( 'guest' ) );
}
/** /**
* Collect runtime metrics from stored data. * Collect runtime metrics from stored data.
* *

View File

@@ -0,0 +1,496 @@
<?php
/**
* Custom metric builder class.
*
* @package WP_Prometheus
*/
namespace Magdev\WpPrometheus\Metrics;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* CustomMetricBuilder class.
*
* Manages custom user-defined Prometheus metrics.
*/
class CustomMetricBuilder {
/**
* Option name for storing custom metrics.
*
* @var string
*/
const OPTION_NAME = 'wp_prometheus_custom_metrics';
/**
* Export format version.
*
* @var string
*/
const EXPORT_VERSION = '1.0.0';
/**
* Maximum number of labels per metric.
*
* @var int
*/
const MAX_LABELS = 5;
/**
* Maximum number of label value combinations per metric.
*
* @var int
*/
const MAX_LABEL_VALUES = 50;
/**
* Get all custom metrics.
*
* @return array
*/
public function get_all(): array {
$metrics = get_option( self::OPTION_NAME, array() );
return is_array( $metrics ) ? $metrics : array();
}
/**
* Get a single metric by ID.
*
* @param string $id Metric ID.
* @return array|null
*/
public function get( string $id ): ?array {
$metrics = $this->get_all();
return $metrics[ $id ] ?? null;
}
/**
* Save a metric (create or update).
*
* @param array $metric Metric data.
* @return string Metric ID.
* @throws \InvalidArgumentException If validation fails.
*/
public function save( array $metric ): string {
$errors = $this->validate( $metric );
if ( ! empty( $errors ) ) {
throw new \InvalidArgumentException( implode( ', ', $errors ) );
}
$metrics = $this->get_all();
// Generate ID if not provided.
if ( empty( $metric['id'] ) ) {
$metric['id'] = wp_generate_uuid4();
$metric['created_at'] = time();
}
$metric['updated_at'] = time();
// Sanitize and normalize the metric data.
$metric = $this->sanitize_metric( $metric );
$metrics[ $metric['id'] ] = $metric;
update_option( self::OPTION_NAME, $metrics );
return $metric['id'];
}
/**
* Delete a metric.
*
* @param string $id Metric ID.
* @return bool True if deleted, false if not found.
*/
public function delete( string $id ): bool {
$metrics = $this->get_all();
if ( ! isset( $metrics[ $id ] ) ) {
return false;
}
unset( $metrics[ $id ] );
update_option( self::OPTION_NAME, $metrics );
return true;
}
/**
* Validate a Prometheus metric name.
*
* @param string $name Metric name.
* @return bool True if valid.
*/
public function validate_name( string $name ): bool {
// Prometheus metric names must match: [a-zA-Z_:][a-zA-Z0-9_:]*
return (bool) preg_match( '/^[a-zA-Z_:][a-zA-Z0-9_:]*$/', $name );
}
/**
* Validate a Prometheus label name.
*
* @param string $name Label name.
* @return bool True if valid.
*/
public function validate_label_name( string $name ): bool {
// Prometheus label names must match: [a-zA-Z_][a-zA-Z0-9_]*
// Labels starting with __ are reserved.
if ( strpos( $name, '__' ) === 0 ) {
return false;
}
return (bool) preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $name );
}
/**
* Validate a complete metric definition.
*
* @param array $metric Metric data.
* @return array Array of error messages (empty if valid).
*/
public function validate( array $metric ): array {
$errors = array();
// Name is required.
if ( empty( $metric['name'] ) ) {
$errors[] = __( 'Metric name is required.', 'wp-prometheus' );
} elseif ( ! $this->validate_name( $metric['name'] ) ) {
$errors[] = __( 'Metric name must start with a letter, underscore, or colon, and contain only letters, numbers, underscores, and colons.', 'wp-prometheus' );
}
// Check for reserved prefixes.
if ( ! empty( $metric['name'] ) ) {
$reserved_prefixes = array( 'wordpress_', 'go_', 'process_', 'promhttp_' );
foreach ( $reserved_prefixes as $prefix ) {
if ( strpos( $metric['name'], $prefix ) === 0 ) {
$errors[] = sprintf(
/* translators: %s: Reserved prefix */
__( 'Metric name cannot start with reserved prefix "%s".', 'wp-prometheus' ),
$prefix
);
break;
}
}
}
// Check for duplicate names (excluding current metric if editing).
if ( ! empty( $metric['name'] ) ) {
$existing = $this->get_all();
foreach ( $existing as $id => $existing_metric ) {
if ( $existing_metric['name'] === $metric['name'] && ( empty( $metric['id'] ) || $metric['id'] !== $id ) ) {
$errors[] = __( 'A metric with this name already exists.', 'wp-prometheus' );
break;
}
}
}
// Help text is required.
if ( empty( $metric['help'] ) ) {
$errors[] = __( 'Help text is required.', 'wp-prometheus' );
}
// Validate type.
$valid_types = array( 'gauge' );
if ( empty( $metric['type'] ) || ! in_array( $metric['type'], $valid_types, true ) ) {
$errors[] = __( 'Invalid metric type. Only gauge is supported.', 'wp-prometheus' );
}
// Validate labels.
if ( ! empty( $metric['labels'] ) ) {
if ( ! is_array( $metric['labels'] ) ) {
$errors[] = __( 'Labels must be an array.', 'wp-prometheus' );
} elseif ( count( $metric['labels'] ) > self::MAX_LABELS ) {
$errors[] = sprintf(
/* translators: %d: Maximum labels */
__( 'Maximum %d labels allowed per metric.', 'wp-prometheus' ),
self::MAX_LABELS
);
} else {
foreach ( $metric['labels'] as $label ) {
if ( ! $this->validate_label_name( $label ) ) {
$errors[] = sprintf(
/* translators: %s: Label name */
__( 'Invalid label name: %s', 'wp-prometheus' ),
$label
);
}
}
}
}
// Validate value type.
$valid_value_types = array( 'static', 'option' );
if ( empty( $metric['value_type'] ) || ! in_array( $metric['value_type'], $valid_value_types, true ) ) {
$errors[] = __( 'Invalid value type. Must be "static" or "option".', 'wp-prometheus' );
}
// Validate value config based on type.
if ( ! empty( $metric['value_type'] ) ) {
if ( 'static' === $metric['value_type'] ) {
// Static values validated in label_values.
} elseif ( 'option' === $metric['value_type'] ) {
if ( empty( $metric['value_config']['option_name'] ) ) {
$errors[] = __( 'Option name is required for option-based metrics.', 'wp-prometheus' );
}
}
}
// Validate label values count.
if ( ! empty( $metric['label_values'] ) && is_array( $metric['label_values'] ) ) {
if ( count( $metric['label_values'] ) > self::MAX_LABEL_VALUES ) {
$errors[] = sprintf(
/* translators: %d: Maximum label combinations */
__( 'Maximum %d label value combinations allowed.', 'wp-prometheus' ),
self::MAX_LABEL_VALUES
);
}
// Validate each row has correct number of values.
$label_count = count( $metric['labels'] ?? array() );
foreach ( $metric['label_values'] as $row ) {
if ( is_array( $row ) && count( $row ) !== $label_count + 1 ) { // +1 for value.
$errors[] = __( 'Each label value row must have values for all labels plus a metric value.', 'wp-prometheus' );
break;
}
}
}
return $errors;
}
/**
* Sanitize metric data.
*
* @param array $metric Raw metric data.
* @return array Sanitized metric data.
*/
private function sanitize_metric( array $metric ): array {
$sanitized = array(
'id' => sanitize_key( $metric['id'] ?? '' ),
'name' => sanitize_key( $metric['name'] ?? '' ),
'help' => sanitize_text_field( $metric['help'] ?? '' ),
'type' => sanitize_key( $metric['type'] ?? 'gauge' ),
'labels' => array(),
'value_type' => sanitize_key( $metric['value_type'] ?? 'static' ),
'value_config' => array(),
'label_values' => array(),
'enabled' => ! empty( $metric['enabled'] ),
'created_at' => absint( $metric['created_at'] ?? time() ),
'updated_at' => absint( $metric['updated_at'] ?? time() ),
);
// Sanitize labels.
if ( ! empty( $metric['labels'] ) && is_array( $metric['labels'] ) ) {
foreach ( $metric['labels'] as $label ) {
$sanitized['labels'][] = sanitize_key( $label );
}
}
// Sanitize value config.
if ( 'static' === $sanitized['value_type'] ) {
$sanitized['value_config'] = array();
} elseif ( 'option' === $sanitized['value_type'] ) {
$sanitized['value_config'] = array(
'option_name' => sanitize_key( $metric['value_config']['option_name'] ?? '' ),
'default' => floatval( $metric['value_config']['default'] ?? 0 ),
);
}
// Sanitize label values.
if ( ! empty( $metric['label_values'] ) && is_array( $metric['label_values'] ) ) {
foreach ( $metric['label_values'] as $row ) {
if ( is_array( $row ) ) {
$sanitized_row = array();
foreach ( $row as $index => $value ) {
// Last value is the metric value (numeric).
if ( $index === count( $row ) - 1 ) {
$sanitized_row[] = floatval( $value );
} else {
$sanitized_row[] = sanitize_text_field( $value );
}
}
$sanitized['label_values'][] = $sanitized_row;
}
}
}
return $sanitized;
}
/**
* Export all metrics to JSON.
*
* @return string JSON string.
*/
public function export(): string {
$metrics = $this->get_all();
$export_data = array(
'version' => self::EXPORT_VERSION,
'plugin_version' => WP_PROMETHEUS_VERSION,
'exported_at' => gmdate( 'c' ),
'site_url' => home_url(),
'metrics' => array_values( $metrics ),
);
return wp_json_encode( $export_data, JSON_PRETTY_PRINT );
}
/**
* Import metrics from JSON.
*
* @param string $json JSON string.
* @param string $mode Import mode: 'skip', 'overwrite', or 'rename'.
* @return array Result with 'imported', 'skipped', 'errors' counts.
* @throws \InvalidArgumentException If JSON is invalid.
*/
public function import( string $json, string $mode = 'skip' ): array {
$data = json_decode( $json, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
throw new \InvalidArgumentException( __( 'Invalid JSON format.', 'wp-prometheus' ) );
}
if ( empty( $data['metrics'] ) || ! is_array( $data['metrics'] ) ) {
throw new \InvalidArgumentException( __( 'No metrics found in import file.', 'wp-prometheus' ) );
}
$result = array(
'imported' => 0,
'skipped' => 0,
'errors' => 0,
'messages' => array(),
);
$existing_metrics = $this->get_all();
$existing_names = array_column( $existing_metrics, 'name', 'id' );
foreach ( $data['metrics'] as $metric ) {
if ( empty( $metric['name'] ) ) {
$result['errors']++;
continue;
}
// Check for name collision.
$name_exists = in_array( $metric['name'], $existing_names, true );
if ( $name_exists ) {
if ( 'skip' === $mode ) {
$result['skipped']++;
$result['messages'][] = sprintf(
/* translators: %s: Metric name */
__( 'Skipped "%s" (already exists).', 'wp-prometheus' ),
$metric['name']
);
continue;
} elseif ( 'rename' === $mode ) {
// Generate unique name.
$base_name = $metric['name'];
$counter = 1;
while ( in_array( $metric['name'], $existing_names, true ) ) {
$metric['name'] = $base_name . '_imported_' . $counter;
$counter++;
}
}
// 'overwrite' mode: continue with same name, will overwrite below.
}
// Clear ID to create new metric (unless overwriting).
if ( 'overwrite' === $mode && $name_exists ) {
// Find existing ID by name.
$metric['id'] = array_search( $metric['name'], $existing_names, true );
} else {
unset( $metric['id'] );
}
try {
$this->save( $metric );
$result['imported']++;
// Update existing names for subsequent collision checks.
$existing_names = array_column( $this->get_all(), 'name', 'id' );
} catch ( \InvalidArgumentException $e ) {
$result['errors']++;
$result['messages'][] = sprintf(
/* translators: 1: Metric name, 2: Error message */
__( 'Error importing "%1$s": %2$s', 'wp-prometheus' ),
$metric['name'],
$e->getMessage()
);
}
}
return $result;
}
/**
* Register custom metrics with the Collector.
*
* @param Collector $collector The metrics collector instance.
* @return void
*/
public function register_with_collector( Collector $collector ): void {
$metrics = $this->get_all();
foreach ( $metrics as $metric ) {
if ( empty( $metric['enabled'] ) ) {
continue;
}
try {
$gauge = $collector->register_gauge(
$metric['name'],
$metric['help'],
$metric['labels'] ?? array()
);
// Set values based on value type.
if ( 'option' === $metric['value_type'] ) {
// Option-based metric: read from WordPress option.
$option_name = $metric['value_config']['option_name'] ?? '';
$default = $metric['value_config']['default'] ?? 0;
if ( ! empty( $option_name ) ) {
$value = get_option( $option_name, $default );
$value = is_numeric( $value ) ? floatval( $value ) : $default;
// For option-based, use empty labels if no labels defined.
$label_values = array();
if ( ! empty( $metric['labels'] ) && ! empty( $metric['label_values'][0] ) ) {
// Use first row of labels (without the value).
$label_values = array_slice( $metric['label_values'][0], 0, count( $metric['labels'] ) );
}
$gauge->set( $value, $label_values );
}
} elseif ( 'static' === $metric['value_type'] ) {
// Static metric: use predefined label values.
if ( ! empty( $metric['label_values'] ) ) {
foreach ( $metric['label_values'] as $row ) {
if ( ! is_array( $row ) || count( $row ) < 1 ) {
continue;
}
// Last element is the value.
$value = array_pop( $row );
// Remaining elements are label values.
$gauge->set( floatval( $value ), $row );
}
} else {
// No labels, single value.
$gauge->set( 0, array() );
}
}
} catch ( \Exception $e ) {
// Log error but don't break metric collection.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf( 'WP Prometheus: Failed to register custom metric "%s": %s', $metric['name'], $e->getMessage() ) );
}
}
}
}
}

View File

@@ -3,7 +3,7 @@
* Plugin Name: WP Prometheus * Plugin Name: WP Prometheus
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus * Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus
* Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics. * Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics.
* Version: 0.1.0 * Version: 0.3.0
* Requires at least: 6.4 * Requires at least: 6.4
* Requires PHP: 8.3 * Requires PHP: 8.3
* Author: Marco Graetsch * Author: Marco Graetsch
@@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* *
* @var string * @var string
*/ */
define( 'WP_PROMETHEUS_VERSION', '0.1.0' ); define( 'WP_PROMETHEUS_VERSION', '0.3.0' );
/** /**
* Plugin file path. * Plugin file path.