diff --git a/composer.json b/composer.json index bce5533..45a73f8 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "scripts": { "lint": "php ./vendor/bin/phpcs -ps .", - "make-pot": "./vendor/bin/wp make-pot ./ languages/prompress.pot --domain=prompress" + "make-pot": "./vendor/bin/wp i18n make-pot ./ languages/prompress.pot --domain=prompress" }, "config": { "sort-packages": true, diff --git a/inc/admin-page.php b/inc/admin-page.php index eca95ce..4ea2527 100644 --- a/inc/admin-page.php +++ b/inc/admin-page.php @@ -46,6 +46,12 @@ function register_page() { */ function render_page() { ?> +
\WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\\storage_compatibility', + 'permission_callback' => function () { + return \current_user_can( 'manage_options' ); + }, ] ); @@ -52,6 +55,9 @@ function register_rest_routes(): void { [ 'methods' => \WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\\storage_wipe', + 'permission_callback' => function () { + return \current_user_can( 'manage_options' ); + }, ] ); } @@ -72,6 +78,27 @@ function metrics_permissions(): bool { return true; } + if ( 'api-key' === $settings['authType'] ) { + $expected_header = strtolower( $settings['headerKey'] ?? '' ); + if ( $expected_header ) { + $headers = \getallheaders(); + + $secret = null; + foreach ( $headers as $key => $value ) { + if ( strtolower( $key ) === $expected_header ) { + $secret = $value; + break; + } + } + + if ( $secret ) { + return ( $settings['headerValue'] ?? null ) === $secret; + } + } + + return false; + } + $auth_header = wp_get_auth_headers(); if (!empty($auth_header['Authorization'])) { if (\preg_match('/Bearer\s(\S+)/', $auth_header['Authorization'], $matches)) { diff --git a/inc/settings.php b/inc/settings.php index c823874..9510f08 100644 --- a/inc/settings.php +++ b/inc/settings.php @@ -36,9 +36,18 @@ function register_settings() { 'authentication' => [ 'type' => 'boolean', ], + 'authType' => [ + 'type' => 'string', + ], 'token' => [ 'type' => 'string', ], + 'headerKey' => [ + 'type' => 'string', + ], + 'headerValue' => [ + 'type' => 'string', + ], 'features' => [ 'type' => 'object', 'properties' => [ @@ -75,6 +84,31 @@ function get_settings(): array { return $settings; } +/** + * Returns prometheus config template. + */ +function get_prometheus_config_template(): string { + $url = rtrim( get_home_url(), '/' ) . '/wp-json/prompress/v1/metrics'; + $parts = wp_parse_url( $url ); + + $template = <<< EOC +scrape_configs: + - job_name: 'live-your-site' + scheme: %scheme% +%auth% + metric_path: '%uri%' + static_configs: + - targets: ['%domain%'] + +EOC; + + return str_replace( + [ '%scheme%', '%uri%', '%domain%' ], + [ $parts['scheme'], $parts['path'], $parts['host'] ], + $template + ); +} + /** * Update settings. */ @@ -112,7 +146,10 @@ function default_settings(): array { 'active' => true, 'storage' => 'apc', 'authentication' => false, + 'authType' => 'bearer', 'token' => '', + 'headerKey' => 'x-prompress-auth', + 'headerValue' => '', 'features' => [ 'emails' => true, 'errors' => true, diff --git a/src/index.tsx b/src/index.tsx index 672f4a6..bf01320 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,12 @@ /** * External Imports. */ -import React from 'react'; -import * as ReactDOMClient from 'react-dom/client'; +import { createRoot } from "react-dom/client"; import { useEffect, useReducer, + useState, } from 'react'; /** @@ -16,6 +16,7 @@ import { __ } from '@wordpress/i18n'; import { Button, + ClipboardButton, Icon, Placeholder, Spinner, @@ -40,15 +41,19 @@ import Notices from './components/Notices'; import './index.scss'; -function Settings() { +function Settings( props ) { const [ state, setState ] = useReducer( ( s, a ) => ({ ...s, ...a }), { isLoaded: false, + configTemplate: '', settings: { active: true, authentication: false, + authType: '', token: '', + headerKey: '', + headerValue: '', storage: 'apc', features: { emails: true, @@ -68,20 +73,44 @@ function Settings() { const { isLoaded, settings, + configTemplate, } = state; const { active, authentication, + authType, token, + headerKey, + headerValue, features, } = settings; useEffect( () => { api.loadPromise.then( () => { - const settings = new api.models.Settings(); if ( false === isLoaded ) { + const initialSettings = props.settings; + + if (initialSettings) { + setState( { + isLoaded: true, + settings: { + active: initialSettings['active'], + authentication: initialSettings['authentication'], + authType: initialSettings['authType'], + token: initialSettings['token'], + headerKey: initialSettings['headerKey'], + headerValue: initialSettings['headerValue'], + features: initialSettings['features'], + }, + configTemplate: props.configTemplate + } ); + + return; + } + + const settings = new api.models.Settings(); settings.fetch() .then( ( response ) => { if ( null !== response['prompress_settings'] ) { @@ -90,7 +119,10 @@ function Settings() { settings: { active: response['prompress_settings']['active'], authentication: response['prompress_settings']['authentication'], + authType: response['prompress_settings']['authType'], token: response['prompress_settings']['token'], + headerKey: response['prompress_settings']['headerKey'], + headerValue: response['prompress_settings']['headerValue'], features: response['prompress_settings']['features'], }, } ); @@ -106,6 +138,27 @@ function Settings() { } ); }, [] ); + const [ + configHasCopied, setConfigHasCopied + ] = useState( false ); + + function buildConfig( template, settings ) { + var authReplacement = ''; + if (settings.authentication) { + if (settings.authType === 'bearer') { + authReplacement = " authorization:\n" + + " type: Bearer\n" + + " credentials: '" + settings.token.replace("'", "''") + "'\n"; + } else if (settings.authType === 'api-key') { + authReplacement = " http_headers:\n " + + settings.headerKey + + ":\n values: ['" + settings.headerValue.replace("'", "''") + "']\n"; + } + } + + return template.replace("%auth%\n", authReplacement); + } + useEffect( () => { apiFetch( { method: 'GET', @@ -128,6 +181,8 @@ function Settings() { ); } + const configText = buildConfig( configTemplate, settings ); + return (+
# prometheus.yaml+
{ configText }
+