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( - '', - 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( + '', + 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 }
- -
-
+ + +
- -
+
- -
+ + @@ -201,8 +203,8 @@

Site Info

-
-
+
- -
+
- -
+
+ +
+ +
+

Persist Site Data

+ + + +
+

Add Key Value Pair

+ + + +
+
+ + + +
+
+ + + +
Add
+
+ + + +
+

Delete Key

+ + + +
+ + + +
Delete
+
+
+ +
+ +
@@ -261,8 +307,8 @@

More Site Options -
-
+

- -
+
- -
+
- -