diff --git a/.wp-env.json b/.wp-env.json index 0b8ee5dc..3aa868ea 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,7 +1,7 @@ { "core": "WordPress/WordPress", - "plugins": [ "./plugin" ], - "themes": [ "./theme" ], + "phpVersion": "8.1", + "config": { "WP_DEBUG": true, "WP_DEBUG_LOG": true, @@ -9,5 +9,9 @@ "SCRIPT_DEBUG": true, "WP_DEVELOPMENT_MODE": "all", "WP_ENVIRONMENT_TYPE": "local" - } + }, + "mappings": { + "wp-content/plugins/wpcloud-station-plugin": "./plugin", + "wp-content/themes/wpcloud-station-theme": "./theme" + } } diff --git a/plugin/blocks/src/components/expanding-section/view.js b/plugin/blocks/src/components/expanding-section/view.js index 149a0479..d66f525c 100644 --- a/plugin/blocks/src/components/expanding-section/view.js +++ b/plugin/blocks/src/components/expanding-section/view.js @@ -1,5 +1,5 @@ ((wpcloud) => { - document.querySelectorAll('.wpcloud-block-expanding-section.click-to-toggle').forEach((section) => { + document.querySelectorAll('.wpcloud-block-expanding-section.click-to-toggle, .wp-block-wpcloud-expanding-header.click-to-toggle').forEach((section) => { section.addEventListener('click', (event) => { const selection = document.getSelection(); if (selection.type === 'Range') { diff --git a/plugin/blocks/src/components/form-input/render.php b/plugin/blocks/src/components/form-input/render.php index 53dfaacc..d0010135 100644 --- a/plugin/blocks/src/components/form-input/render.php +++ b/plugin/blocks/src/components/form-input/render.php @@ -23,16 +23,27 @@ return ''; } -$content = apply_filters( 'wpcloud_block_form_render_field_' . $name, $content, $attributes, $block ); -$content = apply_filters( 'wpcloud_block_form_render_field', $content, $attributes, $block ); +$attributes = apply_filters( 'wpcloud_block_form_field_attributes_' . $name, $attributes, $block ); +$content = apply_filters( 'wpcloud_block_form_render_field_' . $name, $content, $attributes, $block ); +$content = apply_filters( 'wpcloud_block_form_render_field', $content, $attributes, $block ); -$site_mutable_options = WPCloud_Site::get_mutable_options(); +// Check block data for values and options. +$current_value = $attributes['value'] ?? ''; +$options = array(); +$aliases = array(); + +if ( isset( $attributes['options'] ) ) { + + foreach ( $attributes['options'] as $option ) { + $options[ $option['value'] ] = $option['label']; + } +} +// Check mutable options for values and options. +$site_mutable_options = WPCloud_Site::get_mutable_options(); if ( array_key_exists( $name, $site_mutable_options ) ) { $current_value = WPCLOUD_Site::get_detail( get_the_ID(), $name ); - if ( 'ssh_port' === $name ) { - error_log( 'WP Cloud: ' . $current_value ); - } + if ( is_wp_error( $current_value ) ) { error_log( 'WP Cloud: ' . $current_value->get_error_message() ); $current_value = ''; @@ -42,37 +53,41 @@ } if ( 'select' === $input_type ) { - $options = $site_mutable_options[ $name ]['options']; - $aliases = $site_mutable_options[ $name ]['option_aliases'] ?? array(); - $options_html = ''; - if ( ! is_wp_error( $options ) ) { - foreach ( $options as $value => $label ) { - - $selected = selected( $current_value, $value, false ); - // check for option aliases. - if ( ! $selected && array_key_exists( $value, $aliases ) ) { - $selected = selected( $current_value, $aliases[ $value ], false ); - } - $options_html .= sprintf( - '%s', - esc_attr( $value ), - esc_attr( $selected ), - esc_html( $label ) - ); + $options = $site_mutable_options[ $name ]['options'] ?? $options; + $aliases = $site_mutable_options[ $name ]['option_aliases'] ?? $aliases; + } +} + +if ( 'select' === $input_type ) { + $options_html = ''; + if ( ! is_wp_error( $options ) ) { + foreach ( $options as $value => $label ) { + + $selected = selected( $current_value, $value, false ); + // check for option aliases. + if ( ! $selected && array_key_exists( $value, $aliases ) ) { + $selected = selected( $current_value, $aliases[ $value ], false ); } + $options_html .= sprintf( + '%s', + esc_attr( $value ), + esc_attr( $selected ), + esc_html( $label ) + ); } + } - $regex = '/(]*>)(?:\s*]*>.*?<\/option>)*\s*(<\/select>)/'; - $content = preg_replace( $regex, '$1' . $options_html . '$2', $content ); - } else { - $regex = '/(/'; - if ( 'checkbox' === $input_type ) { - if ( $current_value ) { - $content = preg_replace( $regex, '$1 checked />', $content, 1 ); - } - } else { - $content = preg_replace( $regex, '$1 value="' . $current_value . '" />', $content, 1 ); + $regex = '/(]*>)(?:\s*]*>.*?<\/option>)*\s*(<\/select>)/'; + $content = preg_replace( $regex, '$1' . $options_html . '$2', $content ); +} else { + $regex = '/(/'; + if ( 'checkbox' === $input_type ) { + if ( $current_value ) { + $content = preg_replace( $regex, '$1 checked />', $content, 1 ); } + } else { + $content = preg_replace( $regex, '$1 value="' . $current_value . '" />', $content, 1 ); } } + echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped diff --git a/plugin/blocks/src/components/form/view.js b/plugin/blocks/src/components/form/view.js index fbf8d789..3ea34a19 100644 --- a/plugin/blocks/src/components/form/view.js +++ b/plugin/blocks/src/components/form/view.js @@ -1,5 +1,32 @@ ((wpcloud) => { + async function saveDownload( response ) { + const blob = await response.blob(); + const filename = response.headers.get('Content-Disposition').split('filename=')[1]; + + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(blob, filename); + return; + } + const blobURL = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.style.display = 'none'; + link.href = blobURL; + link.setAttribute('download', filename); + + if (typeof link.download === 'undefined') { + link.setAttribute('target', '_blank'); + } + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + setTimeout(() => { + window.URL.revokeObjectURL(blobURL); + }, 100); + } + async function submitFormData(form, data) { form.classList.add('is-loading'); form.classList.remove('is-error'); @@ -60,7 +87,17 @@ } ); - const result = await response.json(); + const contentType = response.headers.get('Content-Type'); + let result = {}; + + if ( contentType.includes('octet-stream')) { + await saveDownload(response); + result.data = { action: 'download' }; + } else if ( contentType.includes('json')) { + result = await response.json(); + } else { + throw new Error('Invalid response type'); + } wpcloud.hooks.doAction( 'wpcloud_form_response', diff --git a/plugin/blocks/src/log-download/block.json b/plugin/blocks/src/log-download/block.json new file mode 100644 index 00000000..b8b3183d --- /dev/null +++ b/plugin/blocks/src/log-download/block.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "wpcloud/log-download", + "version": "0.1.0", + "title": "Download Log File", + "category": "wpcloud", + "icon": "feedback", + "description": "A block to download the log file", + "example": {}, + "supports": { + "html": false + }, + "textdomain": "wpcloud", + "editorScript": "file:./index.js", + "viewScript": "file:./view.js" +} diff --git a/plugin/blocks/src/log-download/index.js b/plugin/blocks/src/log-download/index.js new file mode 100644 index 00000000..405564e7 --- /dev/null +++ b/plugin/blocks/src/log-download/index.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { registerBlockType } from '@wordpress/blocks'; +import { InnerBlocks } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import { inputTemplate } from '../utils/templates'; + +const required = [ + { name: 'type', label: __('Type'), required: true, hint: '', type: 'select', options: { site: __('Site'), error: __('Error') } }, + { name: 'log_start', label: __('Start Time'),required: true, hint: __( 'Logs are only guaranteed for 28 days. A start date before that may return incomplete data.' ), type: 'datetime' }, + { name: 'log_end', label: __('End Time'), required: true, hint: '', type: 'datetime', value: '2024-08-29T14:40' }, +] +const filters = [ + { name: 'page_size', label: __('Page Size'), hint: __('The maximum number of records to retrieve in a single request. Defaults to 500. Max of 10000.'), type: 'number', placeholder: __('Enter page size') }, + { name: 'scroll_id', label: __('Scroll ID'), hint: __( 'String used to specify the next page of data for large queries; the same query arguments as the initial query must be provided with the scroll_id on each subsequent request.' ), type: 'text', placeholder: __('Enter scroll ID') }, + { name: 'sort_order', label: __('Sort Order'), hint: '', type: 'select', options: { asc: __('Ascending'), desc: __('Descending') } }, + + // error filters + { name: 'filter-error__severity', label: __('Log Level'), hint: 'Comma separated log levels. Example: User, Warning ,Deprecated, Fatal error', type: 'text', placeholder: __('Enter log level'), className: 'wpcloud_form_input__optional_log_filter'}, + // site filters + { name: 'filter-site__cached', label: __('Cached'), hint: '', type: 'text', className: 'wpcloud_form_input__option_log_filter' } , + { name: 'filter-site__renderer', label: __('Renderer'), hint: '', type: 'text', className: 'wpcloud_form_input__optional_log_filter' }, + { name: 'filter-site__request_type', label: __('Request Type'), hint: '', type: 'text', className: 'wpcloud_form_input__optional_log_filter' }, + { name: 'filter-site__status', label: __('Status'), hint: '', type: 'text', placeholder: '200,404', className: 'wpcloud_form_input__optional_log_filter' }, + { name: 'filter-site__user_ip', label: __('User IP'), hint: '', type: 'text', placeholder: __('127.0.0.0'), className: 'wpcloud_form_input__optional_log_filter' }, +]; + +const template = [ + [ 'wpcloud/site-details', { metadata: { name: 'Download logs form' } }, + [ + [ 'core/heading', { level: 2, content: __('Download Logs') } ], + [ + 'wpcloud/form', + { + ajax: true, + wpcloudAction: 'log_download', + className: 'wpcloud_block_form__log_download', + }, + [ + ...required.map(inputTemplate), + [ 'wpcloud/expanding-section', { metadata: { name: 'Log Filters' }, clickToToggle: false, hideHeader: false }, + [ + [ 'wpcloud/expanding-header', { className: 'click-to-toggle' }, + [ + ['core/heading', { level: 3, content: __('Filters'), }], + ], + ], + [ 'wpcloud/expanding-content', {}, + [ + ...filters.map(inputTemplate) + ] + ], + ] + ], + [ 'wpcloud/button', { label: __('Download'), type: 'submit' } ], + ] + ], + ] + ] +]; + +registerBlockType(metadata.name, { + edit: () => , + save: () => , +}); diff --git a/plugin/blocks/src/log-download/index.php b/plugin/blocks/src/log-download/index.php new file mode 100644 index 00000000..5f05ec88 --- /dev/null +++ b/plugin/blocks/src/log-download/index.php @@ -0,0 +1,149 @@ + 'Missing required fields.' ), 400 ); + } + + $page_size = $data['page_size']; + if ( empty( $page_size ) ) { + $page_size = 500; + } + + $time_zone = $data['timezone'] ?? 'UTC'; + + $start_date = date_create( $start, new DateTimeZone( $time_zone ) ); + $start_ts = date_timestamp_get( $start_date ); + + $end_date = date_create( $end, new DateTimeZone( $time_zone ) ); + $end_ts = date_timestamp_get( $end_date ); + + $result = null; + + $options = array( + 'page_size' => $page_size, + 'scroll_id' => $data['scroll_id'] ?? null, + 'sort_order' => $data['sort_order'] ?? 'asc', + ); + + switch ( $type ) { + case 'error': + $options['filter'] = wpcloud_block_process_log_filters( array( 'filter-error__severity' ), $data ); + $result = wpcloud_client_site_error_logs( $data['site_id'], $start_ts, $end_ts, $options ); + break; + case 'site': + $site_filters = array( + 'filter-error__severity', + 'filter-site__cached', + 'filter-site__renderer', + 'filter-site__request_type', + 'filter-site__status', + 'filter-site__user_ip', + ); + $options['filter'] = wpcloud_block_process_log_filters( $site_filters, $data ); + $result = wpcloud_client_site_logs( $data['wpcloud_site_id'], $start_ts, $end_ts, $options ); + break; + default: + $response['success'] = false; + $response['message'] = __( 'Unknown log type', 'wpcloud-block' ); + $response['status'] = 400; + return $response; + } + + if ( is_wp_error( $result ) ) { + $response['success'] = false; + $response['message'] = $result->get_error_message(); + $response['status'] = 500; + } + + // We could use the domain name but the site name saves us a request to the API. + $site_name = get_post_field( 'post_name', $data['site_id'] ); + $filename = sprintf( '%s_%s_log_%s--%s.json', $site_name, $type, $start, $end ); + + $log_data = wp_json_encode( $result, JSON_PRETTY_PRINT ); + + header( 'Content-Description: File Transfer' ); + header( 'Content-Type: application/octet-stream' ); + header( 'Content-Disposition: attachment; filename=' . $filename ); + header( 'Content-Transfer-Encoding: binary' ); + header( 'Connection: Keep-Alive' ); + header( 'Expires: 0' ); + header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' ); + header( 'Pragma: public' ); + header( 'Content-Length: ' . strlen( $log_data ) ); + header( 'Connection: close' ); + echo $log_data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + die(); +} +add_filter( 'wpcloud_form_process_log_download', 'wpcloud_block_form_log_download_handler', 10, 2 ); diff --git a/plugin/blocks/src/log-download/view.js b/plugin/blocks/src/log-download/view.js new file mode 100644 index 00000000..ee324a33 --- /dev/null +++ b/plugin/blocks/src/log-download/view.js @@ -0,0 +1,62 @@ +(() => { + + const form = document.querySelector('.wpcloud_block_form__log_download'); + + function switchFilterOptions() { + + } + + let type = form.querySelector('.wpcloud-block-form-input__input[name="type"]')?.value; + if (type) { + type = 'error' === type ? 'site' : 'error'; + const filters = form.querySelectorAll(`[name^="filter-${type}"]`); + filters.forEach((filter) => { + filter.closest('.wpcloud-block-form--input').classList.add('display-none'); + }); + } + + form.querySelector('.wpcloud-block-form-input__input[name="type"]').addEventListener('change', () => { + const filters = form.querySelectorAll(`[name^="filter-"]`); + filters.forEach((filter) => { + filter.value = ''; + filter.closest('.wpcloud-block-form--input').classList.toggle('display-none'); + }); + }); + + function addUserTimezoneToForm(form) { + if (!form || form.querySelector('input[name="timezone"]')) { + return; + } + const date = new Date(); + const offsetMin = date.getTimezoneOffset(); + const hours = `0${Math.abs( offsetMin ) / 60 ^ 0 }`.slice(-2); + const minutes = `0${offsetMin % 60}`.slice(-2); + const sign = offsetMin < 0 ? '+' : '-'; + const offset = `${sign}${hours}${minutes}`; + const timeZoneInput = document.createElement('input'); + timeZoneInput.type = 'hidden'; + timeZoneInput.name = 'timezone'; + timeZoneInput.value = offset; + form.appendChild(timeZoneInput); + } + + function addDatetimeLocalValue(input, date) { + if (input.value) { + return; + } + if ( ! ( date instanceof Date ) ) { + date = new Date(); + } + input.value = date.toISOString().split('T')[0] + 'T' + date.toTimeString().split(':').slice(0,2).join(':'); + const form = input.closest('form'); + addUserTimezoneToForm(form); + } + + + form.querySelectorAll('.wpcloud-block-form-input__input[name="log_end"]').forEach( addDatetimeLocalValue ); + form.querySelectorAll('.wpcloud-block-form-input__input[name="log_start"]').forEach((input) => { + const date = new Date(); + date.setDate(date.getDate() - 7); + addDatetimeLocalValue(input, date); + }); +})(); \ No newline at end of file diff --git a/plugin/blocks/src/utils/templates.js b/plugin/blocks/src/utils/templates.js index c642526d..e5e6ca0b 100644 --- a/plugin/blocks/src/utils/templates.js +++ b/plugin/blocks/src/utils/templates.js @@ -1,7 +1,10 @@ const mutableOptions = window.wpcloud?.siteMutableOptions || {}; -export function inputTemplate({ name, label, options, hint, type, placeholder } ) { - +export function inputTemplate(templateData) { + const { name, label, options, hint, type, placeholder, required, value } = templateData; + if (type == 'datetime') { + console.log(templateData); + } let hintTemplate = null; if ( hint ) { hintTemplate = [ @@ -43,6 +46,8 @@ export function inputTemplate({ name, label, options, hint, type, placeholder } label: '', meta: { name: label }, placeholder, + required, + value }; const input = [ 'wpcloud/form-input' ]; @@ -70,5 +75,8 @@ export function inputTemplate({ name, label, options, hint, type, placeholder } input.push( [ hintTemplate ] ); } + if (type == "datetime") { + console.log(input); + } return input; } \ No newline at end of file diff --git a/plugin/includes/class-wpcloud-cli.php b/plugin/includes/class-wpcloud-cli.php index 44c8a5e9..7c0eca24 100644 --- a/plugin/includes/class-wpcloud-cli.php +++ b/plugin/includes/class-wpcloud-cli.php @@ -93,26 +93,8 @@ protected static function log_result( mixed $result, string $message = '' ): voi } self::log_response( json_decode( json_encode( $result ), true ) ); } - - /** - * Convert bytes to human readable filesize. - * - * @param int $bytes The bytes to convert. - * @param int $dec The decimal places. - * @return string - */ - protected static function human_filesize( $bytes, $dec = 2 ): string { - $size = array( 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ); - $factor = floor( ( strlen( $bytes ) - 1 ) / 3 ); - if ( 0 === $factor ) { - $dec = 0; - } - - return sprintf( "%.{$dec}f %s", $bytes / ( 1024 ** $factor ), $size[ $factor ] ); - } } - /** * WP Cloud CLI Job */ @@ -181,7 +163,7 @@ function ( $site ) { 'id' => $site->atomic_site_id, 'domain' => $site->domain_name, 'created' => $site->created, - 'space_used' => self::human_filesize( $site->space_used ), + 'space_used' => WPCLOUD_Site::readable_size( $site->space_used ), ); }, $sites @@ -244,7 +226,7 @@ public function get( array $args ): void { * @param bool $remote Whether to update the remote site. * @param bool $confirmed Whether to confirm the update. */ - private function _delete( int $site_id, $remote = false, $confirmed = false ) { + private function _delete( int $site_id, $remote = false, $confirmed = false ) { // phpcs:ignore $result = null; if ( ! $confirmed ) { WP_CLI::confirm( sprintf( 'Are you sure you want to delete the site %d ?', $site_id ) ); @@ -384,7 +366,38 @@ public function software( $args, $actions = array() ) { } /** - * Get the site meta. + * Get site logs + * + * @param array $args The arguments. + * @param array $switches The switches. + */ + public function logs( $args, $switches = array() ) { + $this->set_site_id( $args ); + $type = $switches['type'] ?? 'site'; + $file = $switches['file'] ?? false; + + if ( $file ) { + $log_file = WPCLOUD_Site::prepare_log_file( $type, $this->site_id ); + + WP_CLI::success( $log_file ); + return; + } + + $result = 'site' === $type ? wpcloud_client_site_logs( $this->site_id, null, null ) : wpcloud_client_site_error_logs( $this->site_id, null, null ); + if ( is_wp_error( $result ) ) { + WP_CLI::error( $result->get_error_message() ); + return; + } + + self::log( 'Total logs: ' . $result->total_results ); + self::log( 'Next page: ' . $result->scroll_id ); + $logs = wp_json_encode( $result->logs, JSON_PRETTY_PRINT ); + WP_CLI::print_value( $logs ); + } + + + /** + * Set site id * * @param array $args The arguments. */ @@ -429,7 +442,7 @@ function ( $site ) { } /** - * Get the site meta. + * Get the site cpt * * @param array $args The arguments. */ diff --git a/plugin/includes/class-wpcloud-site.php b/plugin/includes/class-wpcloud-site.php index fe715281..25ebe7af 100644 --- a/plugin/includes/class-wpcloud-site.php +++ b/plugin/includes/class-wpcloud-site.php @@ -503,7 +503,7 @@ public static function get_detail( int|WP_Post $post, string $key, ): mixed { return $result->suggested ?? $result->ips ?? ''; case 'site_name': - return $post->post_title; + return get_the_title( $post ); case 'wp_admin_url': $result = wpcloud_client_site_details( $wpcloud_site_id, true ); @@ -702,13 +702,11 @@ public static function is_domain_ssl_valid( string $domain ): bool|WP_Error { * * @param float $bytes The size in bytes. */ - protected static function readable_size( float $bytes = 0 ): string { - if ( $bytes < 1024 ) { - return $bytes . 'B'; - } - $i = max( 1, floor( log( $bytes, 1024 ) ) ); - $units = array( 'B', 'KB', 'MB', 'GB', 'TB' ); - $gigs = round( $bytes / pow( 1024, $i ), 2 ); - return $gigs . 'G'; + public static function readable_size( float $bytes = 0 ): string { + $i = floor( log( $bytes ) / log( 1024 ) ); + + $sizes = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ); + + return sprintf( '%.02F', $bytes / pow( 1024, $i ) ) * 1 . ' ' . $sizes[ $i ]; } } diff --git a/plugin/includes/wpcloud-client.php b/plugin/includes/wpcloud-client.php index 18ae1810..e9648787 100644 --- a/plugin/includes/wpcloud-client.php +++ b/plugin/includes/wpcloud-client.php @@ -486,7 +486,7 @@ function wpcloud_client_data_centers_available( bool $include_no_preference = fa if ( $include_no_preference ) { $available = $result; - $result = array( + $result = array( '' => __( 'No Preference' ), ); foreach ( $available as $key => $name ) { @@ -850,12 +850,71 @@ function wpcloud_client_test_status( int $code = 200, ?string $message = 'statio return $result; } - if ( $message !== $result->message || 'OK' !== $result->message ) { + if ( $message !== $result->message || 'station-status' !== $result->message ) { return new WP_Error( 'failure', 'Unexpected response', array( 'status' => 500 ) ); } return true; } + +/** + * Fetch the site error logs. + * + * @param string $type The type of logs to fetch. Options: 'site-error-logs', 'site-logs'. + * @param integer $wpcloud_site_id The WP Cloud Site ID. + * @param integer $start The start time of the logs to fetch. + * @param integer $end The end time of the logs to fetch. + * @param array $options Optional. Additional options for the log query. + * + * @return stdClass|WP_Error Site error logs on success. WP_Error on error. + */ +function wpcloud_client_logs( string $type, int $wpcloud_site_id, int $start, int $end, $options = array() ): stdClass|WP_Error { + $endpoint = "$type/$wpcloud_site_id"; + + $args = wp_parse_args( + $options, + array( + 'start' => $start, + 'end' => $end, + 'page_size' => 500, + 'scroll_id' => null, + 'filter' => array(), + ) + ); + return wpcloud_client_post( $wpcloud_site_id, $endpoint, $args ); +} + +/** + * Fetch the site error logs. + * + * @param integer $wpcloud_site_id The WP Cloud Site ID. + * @param integer $start Optional. The start time of the logs to fetch. Default: 28 days ago. + * @param integer $end Optional. The end time of the logs to fetch. Default: now. + * @param array $options Optional. Additional options for the log query. + * + * @return stdClass|WP_Error Site error logs on success. WP_Error on error. + */ +function wpcloud_client_site_error_logs( int $wpcloud_site_id, ?int $start, ?int $end, $options = array() ): stdClass|WP_Error { + $start = $start ?? strtotime( '-28 days' ); + $end = $end ?? time(); + return wpcloud_client_logs( 'site-error-logs', $wpcloud_site_id, $start, $end, $options ); +} + +/** + * Fetch the site logs. + * + * @param integer $wpcloud_site_id The WP Cloud Site ID. + * @param integer $start Optional. The start time of the logs to fetch. Default: 28 days ago. + * @param integer $end Optional. The end time of the logs to fetch. Default: now. + * @param array $options Optional. Additional options for the log query. + * + * @return stdClass|WP_Error Site error logs on success. WP_Error on error. + */ +function wpcloud_client_site_logs( int $wpcloud_site_id, ?int $start, ?int $end, $options = array() ): stdClass|WP_Error { + $start = $start ?? strtotime( '-28 days' ); + $end = $end ?? time(); + return wpcloud_client_logs( 'site-logs', $wpcloud_site_id, $start, $end, $options ); +} /** * Make a GET request the WP Cloud API. * diff --git a/plugin/package.json b/plugin/package.json index d6d2075c..cf0b0aa6 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -12,7 +12,7 @@ "lint:js": "wp-scripts lint-js ./blocks/src/**/*.js", "packages-update": "wp-scripts packages-update", "plugin-zip": "wp-scripts plugin-zip", - "start": "npm run start:blocks & npm run start:frontend & npm run start:admin", + "start": "npm run build && npm run start:blocks & npm run start:frontend & npm run start:admin", "build:blocks" : "wp-scripts build --webpack-src-dir=blocks/src/ --output-path=blocks/build/ --webpack-copy-php", "start:blocks" : "wp-scripts start --webpack-src-dir=blocks/src/ --output-path=blocks/build/ --webpack-copy-php", "build:frontend": "wp-scripts build --webpack-src-dir=assets/js/src/ --output-path=assets/js/build/", diff --git a/theme/templates/single-wpcloud_site.html b/theme/templates/single-wpcloud_site.html index ca88372a..067a4e94 100644 --- a/theme/templates/single-wpcloud_site.html +++ b/theme/templates/single-wpcloud_site.html @@ -41,28 +41,28 @@ Sites - + - - + + + + + Admin Only - - - - + + + This section is only visible to Station Admins. - - - + - -Owner { Owner } + +Site Owner { Site Owner } @@ -73,13 +73,13 @@ Admin Only Space Used{ Space Used } - - -Site TypeStagingBillingInternal + + +Site TypeStagingBillingInternal - - + + Space Quota @@ -95,11 +95,11 @@ Space Quota Sets the space quota for a site. Values are integers in gigabytes. - + - - + + Do Not Delete @@ -120,8 +120,8 @@ Do Not Delete - - + + Suspended HTTP Status Code @@ -137,11 +137,11 @@ Suspended HTTP Status Code Suspends a site. The value is the HTTP 4xx status code the site will respond with. The supported statuses are "404", "410", "451", and "480". -404410451480 +404410451480 - - + + Suspend after @@ -157,9 +157,11 @@ Suspend after Suspends a site after a specified time. The value is a unix Timestamp. - + - + + + @@ -201,8 +203,8 @@ Site Info - - + + Static 404 page @@ -212,11 +214,11 @@ Site Info Set how a site responds to requests for non-existent static files. May be set to "lightweight" for simple, fast web server 404s. May be set to "wordpress" to delegate such requests to WordPress. The current default is "wordpress". -LightweightWordPress +LightweightWordPress - - + + WP Version @@ -226,11 +228,11 @@ Site Info Sets the sites WordPress version. -latestpreviousbeta +latestpreviousbeta - - + + PHP Version @@ -246,12 +248,56 @@ PHP Version Sets the sites PHP version. -8.38.28.1 +8.18.28.3 + + + + +Persist Site Data + + + + +Add Key Value Pair + + + + + + + + + + + + + +Add + + + + + +Delete Key + + + + + + + +Delete + + + + + + @@ -261,8 +307,8 @@ More Site Options - - + + Privacy Model @@ -278,11 +324,11 @@ Privacy Model facilitates protection of site assets. May be set to "wp_uploads" to block logged-out requests for WP uploads. If set, an AT_PRIVACY_MODEL constant will be defined in the PHP environment. Use the "site-wordpress-version" endpoint to set "wp_version". -WP Uploads +WP Uploads - - + + Default PHP Connections @@ -298,11 +344,11 @@ Default PHP Connections may be used to either limit allowed concurrent PHP connections or to increase the default number of concurrent connections a site can use if the web server has spare PHP connections capacity. Clients may set any value for a site between 2 and 10; the platform has more leeway if needed. -2345678910 +2345678910 - - + + PHP File Permissions @@ -318,11 +364,11 @@ PHP File Permissions Sets the PHP file system permissions. May be set to `Read/Write`, `Read Only`, or `Logged in` for read only unless logged into WordPress. -Read/WriteRead OnlyRead only unless logged into WordPress +Read/WriteRead OnlyRead only unless logged into WordPress - - + + Photon Sub-sizes @@ -341,8 +387,8 @@ Photon Sub-sizes - - + + Burst PHP Connections @@ -363,8 +409,8 @@ Burst PHP Connections - - + + Canonize Aliases @@ -410,8 +456,8 @@ Defensive Mode - -Enable Defensive mode ( enter minutes; 0 to disable) + +Enable Defensive mode ( enter minutes; 0 to disable) @@ -428,6 +474,18 @@ Defensive Mode SSH Users + + +Disconnect All Users + + + + + +Access via SSH (if disabled, SFTP will be used) + + + @@ -446,16 +504,16 @@ SSH Users - -User + +User - -Password + +Password - -Public Key + +Public Key @@ -546,7 +604,7 @@ SSH User Options - + @@ -584,8 +642,8 @@ Domains - -Add a Domain + +Add a Domain @@ -668,7 +726,7 @@ Domain Options - + @@ -682,7 +740,7 @@ Domain Options - + @@ -718,49 +776,141 @@ Domain Options - - - - -Persist Site Data + + + +Download Logs - - -Add Key Value Pair - + + +TypeSiteError + - - - + + + + +Start Time + + + + + + + + + +Logs are only guaranteed for 28 days. A start date before that may return incomplete data. + + + - - - - + +End Time + - -Add - - + + + +Filters + + - - -Delete Key - + + + + + +Page Size + - - + + + + + + + +The maximum number of records to retrieve in a single request. Defaults to 500. Max of 10000. + + + - -Delete + + + + +Scroll ID + + + + + + + + + +String used to specify the next page of data for large queries; the same query arguments as the initial query must be provided with the scroll_id on each subsequent request. + + + + + + +Sort OrderAscendingDescending + + + + + + +Log Level + + + + + + + + + +Comma separated log levels. Example: User, Warning ,Deprecated, Fatal error + + + + + + +Cached + + + +Renderer + + + +Request Type + + + +Status + + + +User IP + + + + + +Download - - +
This section is only visible to Station Admins.
Sets the space quota for a site. Values are integers in gigabytes.
Suspends a site. The value is the HTTP 4xx status code the site will respond with. The supported statuses are "404", "410", "451", and "480".
Suspends a site after a specified time. The value is a unix Timestamp.
Static 404 page
Set how a site responds to requests for non-existent static files. May be set to "lightweight" for simple, fast web server 404s. May be set to "wordpress" to delegate such requests to WordPress. The current default is "wordpress".
WP Version
Sets the sites WordPress version.
Sets the sites PHP version.
facilitates protection of site assets. May be set to "wp_uploads" to block logged-out requests for WP uploads. If set, an AT_PRIVACY_MODEL constant will be defined in the PHP environment. Use the "site-wordpress-version" endpoint to set "wp_version".
may be used to either limit allowed concurrent PHP connections or to increase the default number of concurrent connections a site can use if the web server has spare PHP connections capacity. Clients may set any value for a site between 2 and 10; the platform has more leeway if needed.
Sets the PHP file system permissions. May be set to `Read/Write`, `Read Only`, or `Logged in` for read only unless logged into WordPress.
Start Time
Logs are only guaranteed for 28 days. A start date before that may return incomplete data.
Page Size
The maximum number of records to retrieve in a single request. Defaults to 500. Max of 10000.
Scroll ID
String used to specify the next page of data for large queries; the same query arguments as the initial query must be provided with the scroll_id on each subsequent request.
Log Level
Comma separated log levels. Example: User, Warning ,Deprecated, Fatal error