Add error/warn-only Vector filter + 5 new pipeline rules + repetition alert
Analyzed a real 1GB accel-ppp log (2026-07-23): only 2,819 of its lines were error:/warn:, and one pattern - "can't determine router address" - repeated 2,746 times over ~4 hours for two specific subscriber interfaces before self-resolving, completely unalerted since no alert covered it. - New pipeline rules for 3 previously-unclassified real message types (radius:dm_coa session not found, mac change detected, dhcpv4 short packet) plus a text-based fallback pair (accelppp_unclassified_error/ warn) for anything not yet specifically classified - needed because Vector-shipped accel-ppp lines have no real syslog PRI header, so the numeric-severity generic_critical_severity rule never fires for this source. - New alert7: group by gl2_remote_ip + event_type, fires on >5 occurrences in 5 minutes - low enough to have caught the real incident within its first cycle, high enough to tolerate a single transient warning. - vector-accel-ppp-setup.md gained a "keep only error/warn" filter option (Step 2b), explicitly documented as a deliberate tradeoff: it also drops RADIUS accounting, so the Servers & Sessions dashboard and session correlation go empty for any server that applies it. Both the with-filter and without-filter full configs are included so the choice is per-server, not global. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
624db7510f
commit
31258eee01
10 changed files with 182 additions and 30 deletions
|
|
@ -453,6 +453,7 @@ parsed and searchable but never page anyone):
|
|||
| Juniper chassis hardware alarm | `CHASSISD_SNMP_TRAP`/`CHASSISD_SNMP_TRAP6` (over temperature, fan, power supply, etc.) - confirmed live against a real `VC.MYRONIVKA` chassis | High |
|
||||
| Abnormal message volume from one server | A single server in the Servers stream sends more than 150,000 messages in a 10-minute window - see "Message-volume (flood) alerts" below | Medium |
|
||||
| Abnormal syslog volume from network equipment | A single device in the Network Equipment stream sends more than 500 messages in a 5-minute window - see "Message-volume (flood) alerts" below | Medium |
|
||||
| Repeated accel-ppp error/warning from one server | The same `event_type` (from `error:`/`warn:` accel-ppp lines) recurs more than 5 times in 5 minutes from one server - catches a stuck session or ongoing condition rather than a single transient warning. Threshold confirmed live against a real incident: `can't determine router address` repeating 2,746 times over ~4 hours for two specific interfaces | Medium |
|
||||
|
||||
That third one is the "universal network equipment problem" catch-all: it
|
||||
doesn't depend on knowing any vendor's specific message format, just the
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ Graylog відхиляє його з помилкою Jackson-поліморфі
|
|||
| Juniper chassis hardware alarm | `CHASSISD_SNMP_TRAP`/`CHASSISD_SNMP_TRAP6` (перегрів, вентилятор, блок живлення тощо) — перевірено наживо на реальному шасі `VC.MYRONIVKA` | High |
|
||||
| Аномальний обсяг повідомлень від одного сервера | Один сервер у стрімі Servers шле понад 150 000 повідомлень за 10-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium |
|
||||
| Аномальний обсяг syslog від мережевого обладнання | Один пристрій у стрімі Network Equipment шле понад 500 повідомлень за 5-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium |
|
||||
| Повторюваний error/warning accel-ppp з одного сервера | Той самий `event_type` (з рядків `error:`/`warn:` accel-ppp) повторюється понад 5 разів за 5 хвилин з одного сервера — ловить завислу сесію чи триваючий стан, а не разове попередження. Поріг підтверджено наживо на реальному інциденті: `can't determine router address` повторився 2 746 разів за ~4 години для двох конкретних інтерфейсів | Medium |
|
||||
|
||||
Третій алерт і є тим самим "універсальним" покриттям проблем мережевого
|
||||
обладнання: він не залежить від знання формату повідомлень конкретного
|
||||
|
|
|
|||
21
alerts/alert7_accelppp_repeated_error_warn.json
Normal file
21
alerts/alert7_accelppp_repeated_error_warn.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"title": "WARNING: repeated accel-ppp error/warning from one server",
|
||||
"description": "The same category of accel-ppp error/warning is repeating from one server - a single occurrence can be transient (a RADIUS retry, a one-off MAC change), but sustained repetition usually means a stuck session or an ongoing condition. Confirmed live on 2026-07-23 against a real 1GB accel-ppp log: 'can't determine router address' repeated 2,746 times over ~4 hours for two specific subscriber interfaces before self-resolving, with no alert firing at the time since this alert didn't exist yet. Threshold (>5 in 5 minutes) is set low enough to have caught that incident within its first cycle, while still tolerating an occasional single warning.",
|
||||
"priority": 2,
|
||||
"alert": true,
|
||||
"config": {
|
||||
"type": "aggregation-v1",
|
||||
"query": "vendor:accel-ppp",
|
||||
"streams": ["__SERVERS_STREAM_ID__"],
|
||||
"group_by": ["gl2_remote_ip", "event_type"],
|
||||
"series": [{"type": "count", "id": "count-", "field": null}],
|
||||
"conditions": {"expression": {"expr": ">", "left": {"expr": "number-ref", "ref": "count-"}, "right": {"expr": "number", "value": 5.0}}},
|
||||
"search_within_ms": 300000,
|
||||
"execute_every_ms": 300000,
|
||||
"event_limit": 50
|
||||
},
|
||||
"field_spec": {},
|
||||
"key_spec": [],
|
||||
"notification_settings": {"grace_period_ms": 900000, "backlog_size": 5},
|
||||
"notifications": [{"notification_id": "__DISCORD_NOTIFICATION_ID__"}]
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "Servers Parsing",
|
||||
"description": "Parses accel-ppp/RADIUS/conntrack syslog",
|
||||
"source": "pipeline \"Servers Parsing\"\nstage 0 match either\n rule \"accelppp_router_address_error\";\n rule \"accelppp_radius_accounting\";\n rule \"accelppp_radius_access_request\";\n rule \"accelppp_radius_server_down\";\n rule \"accelppp_no_radius_servers\";\n rule \"accelppp_auth_failed\";\n rule \"conntrack_table_full\";\n rule \"freeradius_login_ok\";\n rule \"freeradius_login_incorrect\";\n rule \"generic_critical_severity\";\n rule \"accelppp_interface_tag\";\nend"
|
||||
"source": "pipeline \"Servers Parsing\"\nstage 0 match either\n rule \"accelppp_router_address_error\";\n rule \"accelppp_radius_accounting\";\n rule \"accelppp_radius_access_request\";\n rule \"accelppp_radius_server_down\";\n rule \"accelppp_no_radius_servers\";\n rule \"accelppp_auth_failed\";\n rule \"accelppp_radius_coa_session_not_found\";\n rule \"accelppp_mac_change_detected\";\n rule \"accelppp_dhcpv4_short_packet\";\n rule \"conntrack_table_full\";\n rule \"freeradius_login_ok\";\n rule \"freeradius_login_incorrect\";\n rule \"generic_critical_severity\";\n rule \"accelppp_unclassified_error\";\n rule \"accelppp_unclassified_warn\";\n rule \"accelppp_interface_tag\";\nend"
|
||||
}
|
||||
|
|
|
|||
5
rules/rule22_accelppp_radius_coa_session_not_found.json
Normal file
5
rules/rule22_accelppp_radius_coa_session_not_found.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "accelppp_radius_coa_session_not_found",
|
||||
"description": "accel-ppp: RADIUS CoA/Disconnect-Message arrived for a session that no longer exists - found live in a real 1GB accel-ppp log sample (2026-07-23), format: 'warn: radius:dm_coa: session not found'",
|
||||
"source": "rule \"accelppp_radius_coa_session_not_found\"\nwhen\n contains(to_string($message.message), \"radius:dm_coa: session not found\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"radius_coa_session_not_found\");\n set_field(\"severity_tag\", \"warning\");\nend"
|
||||
}
|
||||
5
rules/rule23_accelppp_mac_change_detected.json
Normal file
5
rules/rule23_accelppp_mac_change_detected.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "accelppp_mac_change_detected",
|
||||
"description": "accel-ppp: a subscriber's MAC address changed mid-session - found live in a real 1GB accel-ppp log sample (2026-07-23), format: 'warn: <interface>: mac change detected'. accelppp_interface is filled in by the accelppp_interface_tag fallback rule, not here.",
|
||||
"source": "rule \"accelppp_mac_change_detected\"\nwhen\n contains(to_string($message.message), \"mac change detected\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"mac_change_detected\");\n set_field(\"severity_tag\", \"warning\");\nend"
|
||||
}
|
||||
5
rules/rule24_accelppp_dhcpv4_short_packet.json
Normal file
5
rules/rule24_accelppp_dhcpv4_short_packet.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "accelppp_dhcpv4_short_packet",
|
||||
"description": "accel-ppp: malformed/truncated DHCPv4 packet received - found live in a real 1GB accel-ppp log sample (2026-07-23), format: 'warn: dhcpv4: short packet received' (no interface in the text itself, unlike most other accel-ppp warnings)",
|
||||
"source": "rule \"accelppp_dhcpv4_short_packet\"\nwhen\n contains(to_string($message.message), \"dhcpv4: short packet received\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"dhcpv4_short_packet\");\n set_field(\"severity_tag\", \"warning\");\nend"
|
||||
}
|
||||
5
rules/rule25_accelppp_unclassified_error.json
Normal file
5
rules/rule25_accelppp_unclassified_error.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "accelppp_unclassified_error",
|
||||
"description": "accel-ppp: fallback for any 'error:' line that no specific rule already classified - same catch-all philosophy as generic_critical_severity, but text-based (Vector-shipped accel-ppp lines have no real syslog PRI header, so the numeric severity field is always -1/unparseable and generic_critical_severity never fires for this source).",
|
||||
"source": "rule \"accelppp_unclassified_error\"\nwhen\n !has_field(\"event_type\") && contains(to_string($message.message), \"error:\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"accelppp_unclassified_error\");\n set_field(\"severity_tag\", \"critical\");\nend"
|
||||
}
|
||||
5
rules/rule26_accelppp_unclassified_warn.json
Normal file
5
rules/rule26_accelppp_unclassified_warn.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "accelppp_unclassified_warn",
|
||||
"description": "accel-ppp: fallback for any 'warn:' line that no specific rule already classified - see accelppp_unclassified_error for why a text-based fallback is needed instead of the numeric-severity one.",
|
||||
"source": "rule \"accelppp_unclassified_warn\"\nwhen\n !has_field(\"event_type\") && contains(to_string($message.message), \"warn:\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"accelppp_unclassified_warn\");\n set_field(\"severity_tag\", \"warning\");\nend"
|
||||
}
|
||||
|
|
@ -139,45 +139,46 @@
|
|||
Якщо є помилка — `vector validate` вкаже точний рядок і причину; не
|
||||
запускайте службу, поки ця команда не відповість без помилок.
|
||||
|
||||
## Крок 2b. (Опційно) Фільтрація DHCP-шуму
|
||||
## Крок 2b. (Опційно) Фільтрація — лишити тільки error/warn
|
||||
|
||||
**Навіщо:** `accel-ppp` при кожній оренді/оновленні DHCP пише повний
|
||||
обмін (`recv [DHCPv4 Discover...]`, `send [DHCPv4 Ack...]` тощо) — це
|
||||
основна частина обсягу логів, а не RADIUS/accounting-дані, заради яких
|
||||
власне і збирається моніторинг. Порахували наживо на реальних цифрах
|
||||
(2 активні NAS-сервери, ~634 байти/повідомлення в Graylog): при
|
||||
масштабуванні до 10+ таких серверів це ~38.5 GiB/добу лише "сирих" даних
|
||||
— диск, розрахований на 14-21 день retention, заповниться за лічені дні.
|
||||
Прибирання DHCP на рівні Vector (до того, як пакет узагалі покине цей
|
||||
сервер) — найдешевший спосіб скоротити обсяг, бо не витрачається ні
|
||||
мережевий трафік, ні дисковий IO/CPU Graylog на індексацію того, що
|
||||
однаково ніхто не шукає.
|
||||
**Навіщо:** `accel-ppp` на кожну оренду DHCP, кожен RADIUS
|
||||
accounting-цикл і кожен ipoe-хендшейк пише повний `info`/`debug` обмін.
|
||||
Порахували наживо на реальних цифрах (2 активні NAS-сервери, ~634
|
||||
байти/повідомлення в Graylog): при масштабуванні до 10+ таких серверів
|
||||
це ~38.5 GiB/добу лише "сирих" даних — диск, розрахований на 14-21 день
|
||||
retention, заповниться за лічені дні. У реальному 1GB лозі одного
|
||||
сервера за добу лише **2,819 рядків** виявились `error:`/`warn:` — решта
|
||||
(мільйони рядків) це info/debug-шум.
|
||||
|
||||
Додайте `filter`-transform між `format_for_graylog` і `sinks`. Умова
|
||||
навмисно **не** дропає DHCP-рядки, якщо вони позначені як `error:` чи
|
||||
`warn:` — щоб реальна проблема (наприклад, вичерпання DHCP-пулу чи
|
||||
таймаут оренди) не загубилась разом зі звичайним шумом:
|
||||
**Свідомий компроміс:** цей фільтр лишає **тільки** `error:` і `warn:`,
|
||||
відкидаючи все інше — включно з RADIUS Accounting (сесії, трафік
|
||||
Input/Output-Octets). Якщо для вас важлива аналітика сесій/трафіку
|
||||
(дашборд "Servers & Sessions", кореляція за `accelppp_interface`/
|
||||
`radius_session_id` у Graylog) — **не застосовуйте цей фільтр**, або
|
||||
тримайте один сервер без нього для порівняння. Це рішення per-server: у
|
||||
`vector.yaml` кожного сервера обирається окремо.
|
||||
|
||||
Додайте `filter`-transform між `format_for_graylog` і `sinks`:
|
||||
|
||||
```yaml
|
||||
transforms:
|
||||
format_for_graylog:
|
||||
# ... як у кроці 2, без змін ...
|
||||
|
||||
# Прибирає рутинний DHCP-обмін (Discover/Request/Offer/Ack), АЛЕ
|
||||
# залишає DHCP-рядки, позначені error:/warn: - щоб не втратити реальну
|
||||
# проблему (вичерпання пулу, таймаут оренди тощо) разом зі звичайним
|
||||
# шумом. Умова true = повідомлення проходить далі.
|
||||
drop_dhcp_noise:
|
||||
# Лишає тільки error:/warn: - усе info/debug (RADIUS accounting, DHCP,
|
||||
# ipoe housekeeping) відкидається. Умова true = повідомлення проходить
|
||||
# далі.
|
||||
keep_error_warn_only:
|
||||
type: "filter"
|
||||
inputs:
|
||||
- "format_for_graylog"
|
||||
condition: '!contains(string!(.message), "[DHCPv4 ") || contains(string!(.message), "error:") || contains(string!(.message), "warn:")'
|
||||
condition: 'contains(string!(.message), "error:") || contains(string!(.message), "warn:")'
|
||||
|
||||
sinks:
|
||||
graylog_out:
|
||||
type: "socket"
|
||||
inputs:
|
||||
- "drop_dhcp_noise" # було "format_for_graylog" - тепер через фільтр
|
||||
- "keep_error_warn_only" # було "format_for_graylog" - тепер через фільтр
|
||||
address: "udp://10.254.254.202:5140"
|
||||
mode: "udp"
|
||||
encoding:
|
||||
|
|
@ -189,13 +190,116 @@ sinks:
|
|||
```bash
|
||||
vector validate /etc/vector/vector.yaml
|
||||
sudo systemctl restart vector
|
||||
sudo journalctl -u vector -f # переконайтесь, що йдуть RADIUS-рядки, а DHCPv4 - ні
|
||||
sudo journalctl -u vector -f # переконайтесь, що йдуть лише error:/warn: рядки
|
||||
```
|
||||
|
||||
Якщо для якогось джерела DHCP-дані навпаки потрібні (наприклад, окремий
|
||||
DHCP-сервер, а не accel-ppp) — просто не додавайте `drop_dhcp_noise` у
|
||||
ланцюжок цього джерела; фільтр застосовується лише до того `transform`,
|
||||
який у нього `inputs`.
|
||||
**Знайдені наживо типи `error:`/`warn:`** (з реального 1GB логу,
|
||||
2026-07-23), усі вже покриті pipeline rules у Graylog:
|
||||
- `error: <interface>: can't determine router address` — стабільно
|
||||
повторювався 2,746 разів поспіль ~4 години для двох конкретних
|
||||
інтерфейсів, перш ніж саморозв'язатись; саме цей випадок і привів до
|
||||
появи алерту на повторюваність нижче
|
||||
- `warn: <interface>: radius: server(N) not responding` — чергування між
|
||||
серверами кожні ~30с
|
||||
- `warn: radius:dm_coa: session not found` — CoA/DM-запит на вже
|
||||
завершену сесію
|
||||
- `warn: <interface>: mac change detected` — зміна MAC-адреси абонента
|
||||
під час сесії
|
||||
- `warn: dhcpv4: short packet received` — пошкоджений/обрізаний
|
||||
DHCP-пакет
|
||||
|
||||
Для будь-якого нового типу `error:`/`warn:`, якого ще нема серед
|
||||
специфічних правил, спрацює fallback-правило
|
||||
(`accelppp_unclassified_error`/`accelppp_unclassified_warn`) — воно
|
||||
тегує `event_type` і `severity_tag`, тож нічого не губиться навіть без
|
||||
власного правила.
|
||||
|
||||
### Алерт на повторюваність
|
||||
|
||||
Одне повідомлення `error:`/`warn:` часто нормальне (одна невдала спроба
|
||||
RADIUS, разова зміна MAC) — тривожний сигнал саме **повторюваність**.
|
||||
Алерт `alerts/alert7_accelppp_repeated_error_warn.json` групує за
|
||||
`gl2_remote_ip` + `event_type` і спрацьовує, якщо той самий тип
|
||||
повідомлення з того самого сервера трапляється **>5 разів за 5 хвилин**
|
||||
— поріг підібраний так, щоб гарантовано зловити знайдений
|
||||
"router address"-інцидент у перший же цикл, але не спамити на поодинокі
|
||||
попередження.
|
||||
|
||||
### Повний конфіг — без фільтра
|
||||
|
||||
Базовий варіант (усе: RADIUS accounting, DHCP, ipoe housekeeping,
|
||||
error/warn — усе разом):
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
accel_ppp_log:
|
||||
type: "file"
|
||||
include:
|
||||
- "/var/log/accel-ppp/accel-ppp.log"
|
||||
read_from: "beginning"
|
||||
|
||||
transforms:
|
||||
format_for_graylog:
|
||||
type: "remap"
|
||||
inputs:
|
||||
- "accel_ppp_log"
|
||||
source: |
|
||||
.message = "[" + get_hostname!() + "] " + to_string!(.message)
|
||||
|
||||
sinks:
|
||||
graylog_out:
|
||||
type: "socket"
|
||||
inputs:
|
||||
- "format_for_graylog"
|
||||
address: "udp://10.254.254.202:5140"
|
||||
mode: "udp"
|
||||
encoding:
|
||||
codec: "text"
|
||||
```
|
||||
|
||||
### Повний конфіг — з фільтром "тільки error/warn"
|
||||
|
||||
Той самий конфіг плюс один `transform` і зміна `inputs` у `sinks`:
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
accel_ppp_log:
|
||||
type: "file"
|
||||
include:
|
||||
- "/var/log/accel-ppp/accel-ppp.log"
|
||||
read_from: "beginning"
|
||||
|
||||
transforms:
|
||||
format_for_graylog:
|
||||
type: "remap"
|
||||
inputs:
|
||||
- "accel_ppp_log"
|
||||
source: |
|
||||
.message = "[" + get_hostname!() + "] " + to_string!(.message)
|
||||
|
||||
keep_error_warn_only:
|
||||
type: "filter"
|
||||
inputs:
|
||||
- "format_for_graylog"
|
||||
condition: 'contains(string!(.message), "error:") || contains(string!(.message), "warn:")'
|
||||
|
||||
sinks:
|
||||
graylog_out:
|
||||
type: "socket"
|
||||
inputs:
|
||||
- "keep_error_warn_only"
|
||||
address: "udp://10.254.254.202:5140"
|
||||
mode: "udp"
|
||||
encoding:
|
||||
codec: "text"
|
||||
```
|
||||
|
||||
**Коли який обирати:** без фільтра — коли потрібна аналітика сесій/трафіку
|
||||
(дашборд "Servers & Sessions", кореляція сесій); з фільтром — коли
|
||||
головне економія диска/мережі і головний інтерес саме в проблемах
|
||||
(error/warn + алерт на повторюваність). Рішення приймається окремо для
|
||||
кожного сервера — нічого не заважає тримати частину серверів з фільтром,
|
||||
частину без.
|
||||
|
||||
## Крок 3. Права доступу (запуск від root)
|
||||
|
||||
|
|
@ -383,7 +487,7 @@ Graylog двічі з різними форматами.
|
|||
| Vector запускається, але не бачить оновлень файлу | Файл ротується (logrotate) інакше, ніж очікує Vector | Перевірте `include`/`ignore_older_secs`; за потреби додайте `fingerprint` налаштування у `source`, щоб Vector коректно стежив за файлом після ротації |
|
||||
| `vector validate` видає `error[E103]: unhandled fallible assignment` у `transform` | VRL вимагає явно гарантувати тип операнда перед конкатенацією/операцією (наприклад, `.message` без `to_string!()`) | Обгорніть проблемне поле в `to_string!(...)` (або іншу `!`-функцію відповідного типу) — саме так це вирішено в базовому конфізі кроку 2 |
|
||||
| Критичні алерти в Discord на рутинні `info`-повідомлення від Vector-джерела | Syslog UDP input не бачить валідного `<PRI>` заголовка в сирому тексті Vector, тож Graylog проставляє severity `-1` ("невідомо") — а не 0-2 (справді критично) | Це вже виправлено на стороні Graylog (правило `generic_critical_severity` тепер явно виключає `level < 0`); якщо побачите знову — перевірте, чи не відкотилось це правило |
|
||||
| DHCP-рядки більше не з'являються в Graylog | Це очікувано, якщо додано `drop_dhcp_noise` з кроку 2b | Перевірте `vector.yaml` — якщо DHCP-дані все ж потрібні, приберіть цей transform з ланцюжка `inputs` sink'у |
|
||||
| RADIUS accounting/DHCP/ipoe-рядки більше не з'являються в Graylog | Це очікувано, якщо додано `keep_error_warn_only` з кроку 2b — фільтр лишає тільки error:/warn: | Перевірте `vector.yaml` — якщо потрібна аналітика сесій/трафіку, приберіть цей transform з ланцюжка `inputs` sink'у (див. "Повний конфіг без фільтра" у кроці 2b) |
|
||||
| Алерт "abnormal message volume" щойно після першого запуску Vector на новому сервері | `read_from: "beginning"` (крок 2) означає, що при першому старті Vector вичитує весь наявний лог-файл одразу, а не тільки нові рядки — одноразовий сплеск, не триваюча проблема | Перевірте обсяг від цього джерела через кілька хвилин (`gl2_remote_ip:<ip>` у Search) — має впасти до звичайного рівня |
|
||||
|
||||
## Довідка: структура конфігурації
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue