feat(admin): always-on dashboard widget + default retro days = 0
- Dashboard widget is now registered unconditionally. Header + content adapt: shows '⚠️ ошибки доставки' only when failed > 0, otherwise plain title with queue state (pending / sent-24h / failed). Pilot feedback: the conditional widget felt like the plugin was missing — users expect a persistent status readout. - Retro default window changed from 7 days to 0 (no limit). Most pilot uses were 'dump everything since install', so 0 is the more useful default. User can still narrow to N days in the form. - Spec §10 + §14 updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,15 +7,18 @@ final class Dashboard_Widget
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
add_action('wp_dashboard_setup', [$this, 'maybe_register_widget']);
|
||||
add_action('wp_dashboard_setup', [$this, 'register_widget']);
|
||||
}
|
||||
|
||||
public function maybe_register_widget(): void
|
||||
public function register_widget(): void
|
||||
{
|
||||
if (Queue::count_failed() === 0) return;
|
||||
$failed_count = Queue::count_failed();
|
||||
$title = $failed_count > 0
|
||||
? '⚠️ CF7 Spam → Telegram: ошибки доставки'
|
||||
: 'CF7 Spam → Telegram';
|
||||
wp_add_dashboard_widget(
|
||||
'cf7stg_dashboard_errors',
|
||||
'⚠️ CF7 Spam → Telegram: ошибки доставки',
|
||||
'cf7stg_dashboard_status',
|
||||
$title,
|
||||
[$this, 'render']
|
||||
);
|
||||
}
|
||||
@@ -23,7 +26,10 @@ final class Dashboard_Widget
|
||||
public function render(): void
|
||||
{
|
||||
$failed = Queue::list_failed(10);
|
||||
$count = Queue::count_failed();
|
||||
$failed_count = Queue::count_failed();
|
||||
$pending = Queue::count_by_status(Queue::STATUS_PENDING);
|
||||
$sent_24h = Queue::count_sent_last_24h();
|
||||
$configured = Settings::is_configured();
|
||||
$settings_url = admin_url('options-general.php?page=cf7stg-settings');
|
||||
include CF7STG_PLUGIN_DIR . 'admin/views/dashboard-widget.php';
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ final class Settings_Page
|
||||
{
|
||||
$this->guard('cf7stg_retro');
|
||||
$count = max(1, min(RetroImporter::MAX_COUNT, (int)($_POST['retro_count'] ?? 10)));
|
||||
$days = max(0, (int)($_POST['retro_days'] ?? 7));
|
||||
$days = max(0, (int)($_POST['retro_days'] ?? 0));
|
||||
$repeat = !empty($_POST['retro_repeat']);
|
||||
$result = RetroImporter::import($count, $days, $repeat);
|
||||
$this->redirect_back(['retro_added' => (int)$result['added'], 'retro_skipped' => (int)$result['skipped']]);
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
<?php
|
||||
/** @var array<int,object> $failed */
|
||||
/** @var int $count */
|
||||
/** @var int $failed_count */
|
||||
/** @var int $pending */
|
||||
/** @var int $sent_24h */
|
||||
/** @var bool $configured */
|
||||
/** @var string $settings_url */
|
||||
?>
|
||||
<p>Есть <b><?php echo (int)$count; ?></b> неотправленных сообщений (TG-API отвечает ошибкой):</p>
|
||||
<ul style="margin:0 0 8px 18px;list-style:disc">
|
||||
<?php foreach ($failed as $row): ?>
|
||||
<li>
|
||||
<?php echo esc_html((string)$row->created_at); ?> —
|
||||
<?php echo esc_html((string)$row->last_error); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if (!$configured): ?>
|
||||
<p>⚙️ Плагин не настроен — укажите <b>Bot Token</b> и <b>Chat ID</b> в настройках.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<p>
|
||||
<span title="Ожидают отправки">⏳ В очереди: <b><?php echo (int)$pending; ?></b></span>
|
||||
·
|
||||
<span title="Успешно отправлено за последние сутки">✅ За сутки: <b><?php echo (int)$sent_24h; ?></b></span>
|
||||
·
|
||||
<span title="Не удалось доставить после ретраев">
|
||||
<?php echo $failed_count > 0 ? '⚠️' : '✖'; ?> Ошибки: <b><?php echo (int)$failed_count; ?></b>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<?php if ($failed_count > 0): ?>
|
||||
<p style="margin-top:10px">Последние ошибки доставки (TG-API):</p>
|
||||
<ul style="margin:0 0 8px 18px;list-style:disc">
|
||||
<?php foreach ($failed as $row): ?>
|
||||
<li>
|
||||
<?php echo esc_html((string)$row->created_at); ?> —
|
||||
<?php echo esc_html((string)$row->last_error); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
|
||||
<p><a href="<?php echo esc_url($settings_url); ?>" class="button">Открыть настройки →</a></p>
|
||||
|
||||
@@ -83,7 +83,7 @@ if (isset($_GET['purged'])) $notice = '<div class="notice notice-success"
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Только за последние</th>
|
||||
<td><input type="number" name="retro_days" value="7" min="0"> дней <span class="description">(0 = без ограничения)</span></td>
|
||||
<td><input type="number" name="retro_days" value="0" min="0"> дней <span class="description">(0 = без ограничения)</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
|
||||
@@ -2482,7 +2482,7 @@ final class Settings_Page
|
||||
{
|
||||
$this->guard('cf7stg_retro');
|
||||
$count = max(1, min(RetroImporter::MAX_COUNT, (int)($_POST['retro_count'] ?? 10)));
|
||||
$days = max(0, (int)($_POST['retro_days'] ?? 7));
|
||||
$days = max(0, (int)($_POST['retro_days'] ?? 0));
|
||||
$repeat = !empty($_POST['retro_repeat']);
|
||||
$result = RetroImporter::import($count, $days, $repeat);
|
||||
$this->redirect_back(['retro_added' => (int)$result['added'], 'retro_skipped' => (int)$result['skipped']]);
|
||||
@@ -2605,7 +2605,7 @@ if (isset($_GET['purged'])) $notice = '<div class="notice notice-success"
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Только за последние</th>
|
||||
<td><input type="number" name="retro_days" value="7" min="0"> дней <span class="description">(0 = без ограничения)</span></td>
|
||||
<td><input type="number" name="retro_days" value="0" min="0"> дней <span class="description">(0 = без ограничения)</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
@@ -2660,15 +2660,18 @@ final class Dashboard_Widget
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
add_action('wp_dashboard_setup', [$this, 'maybe_register_widget']);
|
||||
add_action('wp_dashboard_setup', [$this, 'register_widget']);
|
||||
}
|
||||
|
||||
public function maybe_register_widget(): void
|
||||
public function register_widget(): void
|
||||
{
|
||||
if (Queue::count_failed() === 0) return;
|
||||
$failed_count = Queue::count_failed();
|
||||
$title = $failed_count > 0
|
||||
? '⚠️ CF7 Spam → Telegram: ошибки доставки'
|
||||
: 'CF7 Spam → Telegram';
|
||||
wp_add_dashboard_widget(
|
||||
'cf7stg_dashboard_errors',
|
||||
'⚠️ CF7 Spam → Telegram: ошибки доставки',
|
||||
'cf7stg_dashboard_status',
|
||||
$title,
|
||||
[$this, 'render']
|
||||
);
|
||||
}
|
||||
@@ -2676,7 +2679,10 @@ final class Dashboard_Widget
|
||||
public function render(): void
|
||||
{
|
||||
$failed = Queue::list_failed(10);
|
||||
$count = Queue::count_failed();
|
||||
$failed_count = Queue::count_failed();
|
||||
$pending = Queue::count_by_status(Queue::STATUS_PENDING);
|
||||
$sent_24h = Queue::count_sent_last_24h();
|
||||
$configured = Settings::is_configured();
|
||||
$settings_url = admin_url('options-general.php?page=cf7stg-settings');
|
||||
include CF7STG_PLUGIN_DIR . 'admin/views/dashboard-widget.php';
|
||||
}
|
||||
@@ -2688,18 +2694,38 @@ final class Dashboard_Widget
|
||||
```php
|
||||
<?php
|
||||
/** @var array<int,object> $failed */
|
||||
/** @var int $count */
|
||||
/** @var int $failed_count */
|
||||
/** @var int $pending */
|
||||
/** @var int $sent_24h */
|
||||
/** @var bool $configured */
|
||||
/** @var string $settings_url */
|
||||
?>
|
||||
<p>Есть <b><?php echo (int)$count; ?></b> неотправленных сообщений (TG-API отвечает ошибкой):</p>
|
||||
<ul style="margin:0 0 8px 18px;list-style:disc">
|
||||
<?php foreach ($failed as $row): ?>
|
||||
<li>
|
||||
<?php echo esc_html((string)$row->created_at); ?> —
|
||||
<?php echo esc_html((string)$row->last_error); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if (!$configured): ?>
|
||||
<p>⚙️ Плагин не настроен — укажите <b>Bot Token</b> и <b>Chat ID</b> в настройках.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<p>
|
||||
<span title="Ожидают отправки">⏳ В очереди: <b><?php echo (int)$pending; ?></b></span>
|
||||
·
|
||||
<span title="Успешно отправлено за последние сутки">✅ За сутки: <b><?php echo (int)$sent_24h; ?></b></span>
|
||||
·
|
||||
<span title="Не удалось доставить после ретраев">
|
||||
<?php echo $failed_count > 0 ? '⚠️' : '✖'; ?> Ошибки: <b><?php echo (int)$failed_count; ?></b>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<?php if ($failed_count > 0): ?>
|
||||
<p style="margin-top:10px">Последние ошибки доставки (TG-API):</p>
|
||||
<ul style="margin:0 0 8px 18px;list-style:disc">
|
||||
<?php foreach ($failed as $row): ?>
|
||||
<li>
|
||||
<?php echo esc_html((string)$row->created_at); ?> —
|
||||
<?php echo esc_html((string)$row->last_error); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
|
||||
<p><a href="<?php echo esc_url($settings_url); ?>" class="button">Открыть настройки →</a></p>
|
||||
```
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ apply_filters('cf7stg_classifier_thresholds', ['client' => 3, 'spam' => -2]);
|
||||
|
||||
**UI:** на странице настроек — блок «Ретроспективная выгрузка из Flamingo»:
|
||||
- `count` (int, default 10, max 50)
|
||||
- `days` (int, default 7; 0 = без ограничения)
|
||||
- `days` (int, default 0; 0 = без ограничения)
|
||||
- `allow_repeat` (checkbox)
|
||||
- кнопка «Добавить в очередь»
|
||||
|
||||
@@ -324,22 +324,30 @@ $schedules['cf7stg_every_minute'] = ['interval' => 60, 'display' => 'Раз в
|
||||
|
||||
**Ограничения shared-хостинга:** WP-Cron срабатывает при HTTP-запросах к сайту. При малом трафике события могут опаздывать. В UI настроек — короткая подсказка: «Для точности можно настроить внешний cron на `wp-cron.php`».
|
||||
|
||||
## 14. Dashboard-виджет (условный)
|
||||
## 14. Dashboard-виджет
|
||||
|
||||
`add_action('wp_dashboard_setup', ...)` → при `Queue::count_failed() > 0` регистрируется виджет `cf7stg_dashboard_errors`:
|
||||
`add_action('wp_dashboard_setup', ...)` регистрирует виджет `cf7stg_dashboard_status` **всегда** (после пилота: пользователь ожидает увидеть быстрый статус, а не ориентироваться только на появление «тревожной» лампы).
|
||||
|
||||
Заголовок зависит от состояния:
|
||||
- `failed > 0` → `⚠️ CF7 Spam → Telegram: ошибки доставки`
|
||||
- иначе → `CF7 Spam → Telegram`
|
||||
|
||||
Содержимое:
|
||||
|
||||
```
|
||||
⚠️ CF7 Spam → Telegram: ошибки доставки
|
||||
Есть N неотправленных сообщений (TG-API отвечает ошибкой):
|
||||
[если !is_configured: ⚙️ Плагин не настроен — укажите Bot Token и Chat ID в настройках.]
|
||||
|
||||
⏳ В очереди: N · ✅ За сутки: M · [✖|⚠️] Ошибки: K
|
||||
|
||||
[если K > 0:
|
||||
Последние ошибки доставки (TG-API):
|
||||
• {created_at} — {last_error}
|
||||
• ...
|
||||
]
|
||||
|
||||
[Открыть настройки →]
|
||||
```
|
||||
|
||||
При `count_failed() == 0` — виджет не регистрируется, дашборд чистый.
|
||||
|
||||
## 15. Обработка ошибок Telegram API
|
||||
|
||||
`TelegramClient::send_message()` бросает `TelegramException`, у которого метод `is_permanent(): bool`.
|
||||
|
||||
Reference in New Issue
Block a user