| 1 |
<?php |
| 2 |
/** |
| 3 |
* Code Blue — an error-log reader, as an OpenStation app. |
| 4 |
* |
| 5 |
* This file is the window: title, size, icon, the title-bar Refresh |
| 6 |
* button, the ⋯-menu Clear row, the state schema, the server actions, |
| 7 |
* and the DATA the browser paints from. The body itself is rendered |
| 8 |
* in the browser by `code-blue.os.ts` — a function of that state and |
| 9 |
* data — so range, search, sort, legend and expand never wait for a |
| 10 |
* request; only reading a different source, refreshing and clearing |
| 11 |
* round-trip. The log model (discovery, tailing, parsing, clearing) |
| 12 |
* lives in `log-reader.php`. |
| 13 |
* |
| 14 |
* @package OpenStation |
| 15 |
*/ |
| 16 |
|
| 17 |
namespace OpenStation\Apps\CodeBlue; |
| 18 |
|
| 19 |
use OpenStation\App; |
| 20 |
use OpenStation\App\Os; |
| 21 |
use OpenStation\App\State; |
| 22 |
|
| 23 |
// Direct access, unless a standalone host is booting on bare PHP. |
| 24 |
if ( ! defined( 'ABSPATH' ) ) { |
| 25 |
defined( 'OPENSTATION_STANDALONE' ) || exit; |
| 26 |
} |
| 27 |
|
| 28 |
require_once __DIR__ . '/log-reader.php'; |
| 29 |
|
| 30 |
/** A vitals monitor: flatline, a beat, flatline, the live cursor dot. */ |
| 31 |
const ICON = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M6 34 H20 L26 16 L36 50 L42 28 L46 34 H52" fill="none" stroke="currentColor" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/><circle cx="57" cy="34" r="4.5" fill="currentColor"/></svg>'; |
| 32 |
|
| 33 |
/** |
| 34 |
* The source the window is showing: the state's pick when it is |
| 35 |
* still offered, else the first usable one (recorded in the state). |
| 36 |
* |
| 37 |
* @param State $state State. |
| 38 |
* @param array[] $sources Normalised sources. |
| 39 |
* @return array<string,mixed>|null |
| 40 |
*/ |
| 41 |
function current_source( State $state, array $sources ) { |
| 42 |
foreach ( $sources as $source ) { |
| 43 |
if ( $source['id'] === $state->get( 'source' ) && usable( $source ) ) { |
| 44 |
return $source; |
| 45 |
} |
| 46 |
} |
| 47 |
foreach ( $sources as $source ) { |
| 48 |
if ( usable( $source ) ) { |
| 49 |
$state->set( 'source', $source['id'] ); |
| 50 |
return $source; |
| 51 |
} |
| 52 |
} |
| 53 |
return null; |
| 54 |
} |
| 55 |
|
| 56 |
return App::define( 'openstation-code-blue' ) |
| 57 |
->title( __( 'Code Blue', 'desktop-mode' ) ) |
| 58 |
->icon( ICON ) |
| 59 |
->size( 1060, 700 ) |
| 60 |
->min_size( 720, 480 ) |
| 61 |
->placement( 'none' ) |
| 62 |
->desktop_icon( array( 'position' => 24 ) ) |
| 63 |
->can( __NAMESPACE__ . '\\can_use' ) |
| 64 |
->state( |
| 65 |
array( |
| 66 |
'source' => '', |
| 67 |
'range' => '24h', |
| 68 |
'query' => '', |
| 69 |
'sort' => 'recent', |
| 70 |
'hidden' => array(), |
| 71 |
'expanded' => array(), |
| 72 |
'auto' => false, |
| 73 |
'error' => '', |
| 74 |
) |
| 75 |
) |
| 76 |
->title_bar_button( |
| 77 |
'refresh', |
| 78 |
array( |
| 79 |
'label' => __( 'Refresh', 'desktop-mode' ), |
| 80 |
'icon' => 'reload', |
| 81 |
'action' => 'refresh', |
| 82 |
) |
| 83 |
) |
| 84 |
->window_action( |
| 85 |
'clear', |
| 86 |
array( |
| 87 |
'label' => __( 'Clear log', 'desktop-mode' ), |
| 88 |
'icon' => 'dashicons-trash', |
| 89 |
'action' => 'clear', |
| 90 |
'confirm' => array( |
| 91 |
'title' => __( 'Clear this log?', 'desktop-mode' ), |
| 92 |
'message' => __( 'Every entry will be deleted from disk. This cannot be undone.', 'desktop-mode' ), |
| 93 |
'label' => __( 'Clear log', 'desktop-mode' ), |
| 94 |
'danger' => true, |
| 95 |
), |
| 96 |
) |
| 97 |
) |
| 98 |
// Re-reading the log is the whole action; `data()` below does it. |
| 99 |
->action( |
| 100 |
'refresh', |
| 101 |
static function ( State $state ) { |
| 102 |
$state->set( 'error', '' ); |
| 103 |
} |
| 104 |
) |
| 105 |
->action( |
| 106 |
'source', |
| 107 |
static function ( State $state ) { |
| 108 |
$state->reset( 'expanded' )->set( 'error', '' ); |
| 109 |
} |
| 110 |
) |
| 111 |
->action( |
| 112 |
'clear', |
| 113 |
static function ( State $state, Os $os ) { |
| 114 |
$source = current_source( $state, sources( $os ) ); |
| 115 |
if ( ! $source ) { |
| 116 |
return; |
| 117 |
} |
| 118 |
$result = clear( $os, $source ); |
| 119 |
$state->reset( 'expanded' )->set( 'error', true === $result ? '' : $result ); |
| 120 |
if ( true === $result ) { |
| 121 |
$os->toast( __( 'Log cleared.', 'desktop-mode' ) ); |
| 122 |
} |
| 123 |
} |
| 124 |
) |
| 125 |
->data( |
| 126 |
static function ( State $state, Os $os ) { |
| 127 |
$sources = sources( $os ); |
| 128 |
$source = current_source( $state, $sources ); |
| 129 |
$read = $source ? read( $os, $source ) : null; |
| 130 |
return array( |
| 131 |
'sources' => $sources, |
| 132 |
'source' => $source, |
| 133 |
'environment' => environment( $os ), |
| 134 |
'entries' => $read ? $read['entries'] : array(), |
| 135 |
'scanned' => $read ? $read['scanned_bytes'] : 0, |
| 136 |
'truncated' => $read ? $read['truncated'] : false, |
| 137 |
'readError' => $read ? $read['error'] : '', |
| 138 |
'now' => time(), |
| 139 |
'searchUrl' => search_url( $os ), |
| 140 |
); |
| 141 |
} |
| 142 |
); |
| 143 |
|