From 79b36a320aa31b3aa51754eaf6bbd677ad68053e Mon Sep 17 00:00:00 2001 From: Namith Jawahar <48271037+namithj@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:21:39 +0530 Subject: [PATCH] Clear Logs and View Logs Functionality Added (#168) --- assets/css/aspire-update.css | 214 ++++++++++++++++--------- assets/js/aspire-update.js | 128 ++++++++++++++- includes/class-admin-settings.php | 3 +- includes/class-controller.php | 63 ++++++++ includes/class-debug.php | 120 +++++++++++--- includes/views/page-admin-settings.php | 9 ++ 6 files changed, 437 insertions(+), 100 deletions(-) diff --git a/assets/css/aspire-update.css b/assets/css/aspire-update.css index 06856c1..7eea8b2 100644 --- a/assets/css/aspire-update.css +++ b/assets/css/aspire-update.css @@ -1,42 +1,42 @@ @keyframes glow { - 0% { - background-color: rgba(255, 223, 0, 0.1); - } + 0% { + background-color: rgba(255, 223, 0, 0.1); + } - 100% { - background-color: none; - } + 100% { + background-color: none; + } } .glow-reveal { - animation: glow 0.5s ease-in-out; + animation: glow 0.5s ease-in-out; } .aspireupdate-settings-field-hosts-wrapper .aspireupdate-settings-field-hosts-row { - margin: 0 0 10px; + margin: 0 0 10px; } #aspireupdate-generate-api-key { - display: inline-block; - width: 30px; - height: 30px; - background: url(../images/icon-key.svg) no-repeat center center / 24px 24px; - background-color: #cbcbcb; - border: 1px solid #8c8f94; - border-radius: 3px; - clip-path: inset(0 0 0 0); - color: transparent; - cursor: pointer; - transition: background-color 0.3s ease; + display: inline-block; + width: 30px; + height: 30px; + background: url(../images/icon-key.svg) no-repeat center center / 24px 24px; + background-color: #cbcbcb; + border: 1px solid #8c8f94; + border-radius: 3px; + clip-path: inset(0 0 0 0); + color: transparent; + cursor: pointer; + transition: background-color 0.3s ease; } #aspireupdate-generate-api-key:hover { - background-color: #e3e3e3; + background-color: #e3e3e3; } .aspireupdate-settings-field-wrapper p.error { - color: #bc3b3b; - display: none; + color: #bc3b3b; + display: none; } .aspireupdate-notice { @@ -47,88 +47,138 @@ } .aspireupdate-notice p::before { - content: ''; + content: ""; display: inline-block; - margin-inline-end: .5em; + margin-inline-end: 0.5em; vertical-align: middle; - background: url(../images/aspirepress-logo-icon.svg) no-repeat center center / 20px 20px; - height: 20px; - width: 20px; + background: url(../images/aspirepress-logo-icon.svg) no-repeat center center / 20px 20px; + height: 20px; + width: 20px; } -#voltron { - color: transparent; - font-size: clamp(4px, 0.9vw, 8px); - line-height: 6px; - display: inline-block; - cursor: default; +.button.button-clearlog { + border-color: #b32d2e; + color: #b32d2e; } -#voltron:hover { - animation: blink 1.8s ease-in-out infinite; - animation-delay: 5s; +#aspireupdate-log-viewer { + position: fixed; + width: 100%; + height: 100%; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999998; + display: none; } -@keyframes blink { - 0%, - 100% { - color: inherit; - } - 50% { - color: transparent; - } +#aspireupdate-log-viewer:before { + content: ""; + position: fixed; + width: 100%; + height: 100%; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999998; + background: rgba(255, 255, 255, 0.9); +} + +#aspireupdate-log-viewer .outer { + position: fixed; + width: calc(100% - 60px); + height: calc(100% - 60px); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: lightgray; + z-index: 999999; + box-shadow: 0 0 5px #000; + padding: 15px; +} + +#aspireupdate-log-viewer .outer span.close { + position: fixed; + right: -1px; + z-index: 999999; + display: block; + width: 20px; + height: 20px; + line-height: 20px; + font-size: 20px; + top: 1px; + text-align: center; + font-weight: 600; + cursor: pointer; } -@media only screen and (max-width: 576px) { - #voltron { - display: none; - } -======= +#aspireupdate-log-viewer .outer span.close:before, +#aspireupdate-log-viewer .outer span.close:after { + position: absolute; + top: 50%; + left: 50%; + width: 4px; + height: 20px; + background-color: #b32d2e; + transform: rotate(45deg) translate(-50%, -50%); + transform-origin: top left; + content: ''; +} -@keyframes glow { - 0% { - background-color: rgba(255, 223, 0, 0.1); - } +#aspireupdate-log-viewer .outer span.close:after { + transform: rotate(-45deg) translate(-50%, -50%); +} - 100% { - background-color: none; - } +#aspireupdate-log-viewer .inner { + position: fixed; + width: calc(100% - 40px); + height: calc(100% - 40px); + top: 20px; + right: 20px; + bottom: 20px; + left: 20px; + counter-reset: line; + overflow: auto; } -.glow-reveal { - animation: glow 0.5s ease-in-out; +#aspireupdate-log-viewer .inner div { + display: flex; + align-items: flex-start; } -.aspireupdate-settings-field-hosts-wrapper .aspireupdate-settings-field-hosts-row { - margin: 0 0 10px; +#aspireupdate-log-viewer .inner>div:nth-child(odd) { + background-color: #e9e9e9; } -#aspireupdate-generate-api-key { - display: inline-block; - width: 30px; - height: 30px; - background: url(../images/icon-key.svg) no-repeat center center / 24px 24px; - background-color: #cbcbcb; - border: 1px solid #8c8f94; - border-radius: 3px; - clip-path: inset(0 0 0 0); - color: transparent; - cursor: pointer; - transition: background-color 0.3s ease; +#aspireupdate-log-viewer .inner>div:nth-child(even) { + background-color: #e0e0e0; } -#aspireupdate-generate-api-key:hover { - background-color: #e3e3e3; +#aspireupdate-log-viewer .inner .number { + counter-increment: line; + width: 60px; + text-align: right; + padding-right: 10px; + font-weight: bold; + color: #555; } -.aspireupdate-settings-field-wrapper p.error { - color: #bc3b3b; - display: none; +#aspireupdate-log-viewer .inner .number::before { + content: counter(line); +} + +#aspireupdate-log-viewer .inner .content { + white-space: pre-wrap; + word-break: break-all; + margin: 0; + flex: 1; } #voltron { color: transparent; - font-size: 8px; + font-size: clamp(4px, 0.9vw, 8px); line-height: 6px; display: inline-block; cursor: default; @@ -140,11 +190,19 @@ } @keyframes blink { + 0%, 100% { color: inherit; } + 50% { color: transparent; } } + +@media only screen and (max-width: 576px) { + #voltron { + display: none; + } +} diff --git a/assets/js/aspire-update.js b/assets/js/aspire-update.js index dd6e76e..eeaa95c 100644 --- a/assets/js/aspire-update.js +++ b/assets/js/aspire-update.js @@ -2,8 +2,128 @@ jQuery(document).ready(function () { new ApiRewrites(); new ApiDebug(); + + new ClearLog(); + new ViewLog(); }); +class ClearLog { + constructor() { + ClearLog.clearlog_button.init(); + } + + static clearlog_button = { + field: jQuery('#aspireupdate-button-clearlog'), + init() { + ClearLog.clearlog_button.field.click(function () { + ClearLog.clearlog_button.clear(); + }); + }, + show() { + ClearLog.clearlog_button.field.show(); + }, + hide() { + ClearLog.clearlog_button.field.hide(); + }, + clear() { + let parameters = { + "url": aspireupdate.ajax_url, + "type": "POST", + "data": { + "nonce": aspireupdate.nonce, + "action": "aspireupdate_clear_log" + } + }; + jQuery.ajax(parameters) + .done(function (response) { + if ('' != response.data.message) { + alert(response.data.message); + } else { + alert(aspireupdate.unexpected_error); + } + }) + .fail(function (response) { + alert(aspireupdate.unexpected_error); + }); + }, + } +} + +class ViewLog { + constructor() { + ViewLog.viewlog_button.init(); + ViewLog.viewlog_popup.init(); + } + + static viewlog_button = { + field: jQuery('#aspireupdate-button-viewlog'), + init() { + ViewLog.viewlog_button.field.click(function () { + ViewLog.viewlog_popup.show(); + }); + }, + show() { + ViewLog.viewlog_button.field.show(); + }, + hide() { + ViewLog.viewlog_button.field.hide(); + } + } + + static viewlog_popup = { + field: jQuery('#aspireupdate-log-viewer'), + popup_inner: jQuery('#aspireupdate-log-viewer .inner'), + close_button: jQuery('#aspireupdate-log-viewer span.close'), + init() { + ViewLog.viewlog_popup.close_button.click(function () { + ViewLog.viewlog_popup.close(); + }); + + jQuery(document).keydown(function (event) { + if ((event.keyCode === 27) && ViewLog.viewlog_popup.field.is(':visible')) { + ViewLog.viewlog_popup.close(); + } + }); + }, + show() { + let parameters = { + "url": aspireupdate.ajax_url, + "type": "POST", + "data": { + "nonce": aspireupdate.nonce, + "action": "aspireupdate_read_log" + } + }; + jQuery.ajax(parameters) + .done(function (response) { + if ((true == response.success) && ('' != response.data.content)) { + let lines = response.data.content.split(aspireupdate.line_ending); + jQuery.each(lines, function (index, line) { + jQuery('
') + .append( + jQuery('').addClass('number'), + jQuery('').addClass('content').text(line) + ) + .appendTo(ViewLog.viewlog_popup.popup_inner); + }); + ViewLog.viewlog_popup.field.show(); + } else if ('' != response.data.message) { + alert(response.data.message); + } else { + alert(aspireupdate.unexpected_error); + } + }) + .fail(function (response) { + alert(aspireupdate.unexpected_error); + }); + }, + close() { + ViewLog.viewlog_popup.field.hide(); + ViewLog.viewlog_popup.popup_inner.html(''); + } + } +} + class ApiRewrites { constructor() { ApiRewrites.host_selector.init(); @@ -150,7 +270,7 @@ class ApiRewrites { if ((response.status === 400) || (response.status === 401)) { ApiRewrites.api_key.show_error(response.responseJSON?.error); } else { - ApiRewrites.api_key.show_error(aspireupdate.string_unexpected_error + ' ' + response.status); + ApiRewrites.api_key.show_error(aspireupdate.unexpected_error + ' : ' + response.status); } }); }, @@ -192,7 +312,7 @@ class ApiDebug { init() { ApiDebug.enabled_debug.sub_fields = [ ApiDebug.debug_type, - ApiDebug.disable_ssl_verification + ApiDebug.disable_ssl_verification, ]; ApiDebug.enabled_debug.field.change(function () { @@ -205,9 +325,13 @@ class ApiDebug { }, show_options() { Fields.show(ApiDebug.enabled_debug.sub_fields); + ViewLog.viewlog_button.show(); + ClearLog.clearlog_button.show(); }, hide_options() { Fields.hide(ApiDebug.enabled_debug.sub_fields); + ViewLog.viewlog_button.hide(); + ClearLog.clearlog_button.hide(); } } diff --git a/includes/class-admin-settings.php b/includes/class-admin-settings.php index e02f792..339aa99 100644 --- a/includes/class-admin-settings.php +++ b/includes/class-admin-settings.php @@ -295,7 +295,8 @@ public function admin_enqueue_scripts( $hook ) { 'ajax_url' => network_admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'aspireupdate-ajax' ), 'domain' => Utilities::get_top_level_domain(), - 'string_unexpected_error' => esc_html__( 'Unexpected Error:', 'AspireUpdate' ), + 'line_ending' => PHP_EOL, + 'unexpected_error' => esc_html__( 'Unexpected Error', 'AspireUpdate' ), ] ); } diff --git a/includes/class-controller.php b/includes/class-controller.php index d573ecd..43b41c7 100644 --- a/includes/class-controller.php +++ b/includes/class-controller.php @@ -21,6 +21,8 @@ public function __construct() { Branding::get_instance(); $this->api_rewrite(); add_action( 'init', [ $this, 'load_textdomain' ] ); + add_action( 'wp_ajax_aspireupdate_clear_log', [ $this, 'clear_log' ] ); + add_action( 'wp_ajax_aspireupdate_read_log', [ $this, 'read_log' ] ); } /** @@ -48,8 +50,69 @@ private function api_rewrite() { } } + /** + * Ajax action to clear the Log file. + * + * @return void + */ + public function clear_log() { + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'aspireupdate-ajax' ) ) { + wp_send_json_error( + [ + 'message' => __( 'Error: You are not authorized to access this resource.', 'AspireUpdate' ), + ] + ); + } + + $status = Debug::clear(); + if ( is_wp_error( $status ) ) { + wp_send_json_error( + [ + 'message' => $status->get_error_message(), + ] + ); + } + + wp_send_json_success( + [ + 'message' => __( 'Log file cleared successfully.', 'AspireUpdate' ), + ] + ); + } + + /** + * Ajax action to read the Log file. + * + * @return void + */ + public function read_log() { + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'aspireupdate-ajax' ) ) { + wp_send_json_error( + [ + 'message' => __( 'Error: You are not authorized to access this resource.', 'AspireUpdate' ), + ] + ); + } + + $content = Debug::read( 1000 ); + if ( is_wp_error( $content ) ) { + wp_send_json_error( + [ + 'message' => $content->get_error_message(), + ] + ); + } + + wp_send_json_success( + [ + 'content' => $content, + ] + ); + } + /** * Load translations. + * * @return void */ public function load_textdomain() { diff --git a/includes/class-debug.php b/includes/class-debug.php index 5a64b24..182cc02 100644 --- a/includes/class-debug.php +++ b/includes/class-debug.php @@ -19,6 +19,15 @@ class Debug { */ private static $log_file = 'debug-aspire-update.log'; + /** + * Get the Log file path. + * + * @return string The Log file path. + */ + private static function get_file_path() { + return WP_CONTENT_DIR . '/' . self::$log_file; + } + /** * Initializes the WordPress Filesystem. * @@ -36,14 +45,13 @@ private static function init_filesystem() { } /** - * Logs a message to the debug log file. + * Checks the filesystem status and logs error to debug log. * - * @param mixed $message The message to log. - * @param string $type The log level ('string', 'request', 'response'). + * @param WP_Filesystem_Base $wp_filesystem The filesystem object. + * + * @return boolean true on success and false on failure. */ - public static function log( $message, $type = 'string' ) { - $wp_filesystem = self::init_filesystem(); - + private static function verify_filesystem( $wp_filesystem ) { if ( ! $wp_filesystem ) { if ( defined( 'WP_DEBUG' ) && @@ -58,29 +66,97 @@ public static function log( $message, $type = 'string' ) { error_log( 'AspireUpdate - Could not open or write to the file system. Check file system permissions to debug log directory.' ); // phpcs:enable } - return; + return false; } + return true; + } - $timestamp = gmdate( 'Y-m-d H:i:s' ); - $formatted_message = sprintf( - "[%s] [%s]: %s\n" . PHP_EOL, - $timestamp, - strtoupper( $type ), - self::format_message( $message ) - ); + /** + * Get the content of the log file truncated upto N number of lines. + * + * @param integer $limit Max no of lines to return. Defaults to a 1000 lines. + * + * @return string|WP_Error The File content truncate upto the number of lines set in the limit parameter. + */ + public static function read( $limit = 1000 ) { + $wp_filesystem = self::init_filesystem(); + $file_path = self::get_file_path(); + if ( ! self::verify_filesystem( $wp_filesystem ) || ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_readable( $file_path ) ) { + return new \WP_Error( 'not_readable', __( 'Error: Unable to read the log file.', 'AspireUpdate' ) ); + } - $file_path = WP_CONTENT_DIR . '/' . self::$log_file; + $file_content = $wp_filesystem->get_contents_array( $file_path ); + $content = ''; + $index = 0; + foreach ( $file_content as $file_content_lines ) { + if ( ( $index < $limit ) ) { + $content .= $file_content_lines . PHP_EOL; + ++$index; + } + } + if ( '' === trim( $content ) ) { + $content = esc_html__( '*****Log file is empty.*****', 'AspireUpdate' ); + } elseif ( $limit < count( $file_content ) ) { + $content .= PHP_EOL . sprintf( + /* translators: 1: The number of lines at which the content was truncated. */ + esc_html__( '*****Log truncated at %s lines.*****', 'AspireUpdate' ), + $limit + ); + } + return $content; + } - $content = ''; - if ( $wp_filesystem->exists( $file_path ) ) { - $content = $wp_filesystem->get_contents( $file_path ); + /** + * Clear content of the log file. + * + * @return boolean|WP_Error true on success and false on failure. + */ + public static function clear() { + $wp_filesystem = self::init_filesystem(); + $file_path = self::get_file_path(); + if ( ! self::verify_filesystem( $wp_filesystem ) || ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_writable( $file_path ) ) { + return new \WP_Error( 'not_accessible', __( 'Error: Unable to access the log file.', 'AspireUpdate' ) ); } $wp_filesystem->put_contents( $file_path, - $content . $formatted_message, + '', FS_CHMOD_FILE ); + return true; + } + + /** + * Logs a message to the debug log file. + * + * @param mixed $message The message to log. + * @param string $type The log level ('string', 'request', 'response'). + */ + public static function log( $message, $type = 'string' ) { + $wp_filesystem = self::init_filesystem(); + if ( self::verify_filesystem( $wp_filesystem ) ) { + $timestamp = gmdate( 'Y-m-d H:i:s' ); + $formatted_message = sprintf( + '[%s] [%s]: %s', + $timestamp, + strtoupper( $type ), + self::format_message( $message ) + ) . PHP_EOL; + + $file_path = self::get_file_path(); + + $content = ''; + if ( $wp_filesystem->exists( $file_path ) ) { + if ( $wp_filesystem->is_readable( $file_path ) ) { + $content = $wp_filesystem->get_contents( $file_path ); + } + } + $wp_filesystem->put_contents( + $file_path, + $formatted_message . $content, + FS_CHMOD_FILE + ); + } } /** @@ -105,6 +181,8 @@ private static function format_message( $message ) { * Log an info message. * * @param mixed $message The message to log. + * + * @return void */ public static function log_string( $message ) { $admin_settings = Admin_Settings::get_instance(); @@ -119,6 +197,8 @@ public static function log_string( $message ) { * Log a warning message. * * @param mixed $message The message to log. + * + * @return void */ public static function log_request( $message ) { $admin_settings = Admin_Settings::get_instance(); @@ -133,6 +213,8 @@ public static function log_request( $message ) { * Log an error message. * * @param mixed $message The message to log. + * + * @return void */ public static function log_response( $message ) { $admin_settings = Admin_Settings::get_instance(); diff --git a/includes/views/page-admin-settings.php b/includes/views/page-admin-settings.php index 5174d2e..4649029 100644 --- a/includes/views/page-admin-settings.php +++ b/includes/views/page-admin-settings.php @@ -15,7 +15,16 @@ + +

+
+
+ +
+
+
+