mirror of
https://github.com/opnsense/plugins.git
synced 2025-12-10 10:30:21 -06:00
crowdsec: migrate bootgrid -> UIBootGrid (#4816)
* bootgrid -> UIBootGrid * server-side filtering, pagination etc. * version bump * add some field defaults; lint
This commit is contained in:
parent
259fb1ebb8
commit
3b95aa598f
@ -1,5 +1,5 @@
|
||||
PLUGIN_NAME= crowdsec
|
||||
PLUGIN_VERSION= 1.0.10
|
||||
PLUGIN_VERSION= 1.0.11
|
||||
PLUGIN_DEPENDS= crowdsec
|
||||
PLUGIN_COMMENT= Lightweight and collaborative security engine
|
||||
PLUGIN_MAINTAINER= marco@crowdsec.net
|
||||
|
||||
@ -8,6 +8,11 @@ WWW: https://crowdsec.net/
|
||||
Plugin Changelog
|
||||
================
|
||||
|
||||
1.0.11
|
||||
|
||||
* convert tables to UIBootGrid (required for opnsense 25.7)
|
||||
* separate page for each table
|
||||
|
||||
1.0.10
|
||||
|
||||
* changed alias names crowdsec*blacklists -> crowdsec*blocklists
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class AlertsController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class AlertsController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/alerts');
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\CrowdSec;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
@ -14,6 +13,48 @@ use OPNsense\Core\Backend;
|
||||
*/
|
||||
class AlertsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Format scope and value as "scope:value"
|
||||
*
|
||||
* @param array $source Array with 'scope' and 'value' keys (can be a decision)
|
||||
* @return string Formatted string
|
||||
*/
|
||||
private function formatScopeValue(array $source): string
|
||||
{
|
||||
$scope = $source['scope'] ?? '';
|
||||
if ($source['value'] !== '') {
|
||||
$scope = $scope . ':' . $source['value'];
|
||||
}
|
||||
return $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize decision types as "type1:count1 type2:count2 ..."
|
||||
*
|
||||
* @param array $decisions List of decision arrays
|
||||
* @return string Summary string
|
||||
*/
|
||||
private function formatDecisions(array $decisions): string
|
||||
{
|
||||
$counts = [];
|
||||
|
||||
foreach ($decisions as $decision) {
|
||||
if (!isset($decision['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $decision['type'];
|
||||
$counts[$type] = ($counts[$type] ?? 0) + 1;
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
foreach ($counts as $type => $count) {
|
||||
$parts[] = "{$type}:{$count}";
|
||||
}
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve list of alerts
|
||||
*
|
||||
@ -21,13 +62,27 @@ class AlertsController extends ApiControllerBase
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec alerts-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $result;
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
return ["message" => "unable to list alerts"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($result as $alert) {
|
||||
$source = $alert['source'] ?? [];
|
||||
$rows[] = [
|
||||
'id' => $alert['id'],
|
||||
'value' => $this->formatScopeValue($source ?? []),
|
||||
'reason' => $alert['scenario'] ?? '',
|
||||
'country' => $source['cn'] ?? '',
|
||||
'as' => $source['as_name'] ?? '',
|
||||
'decisions' => $this->formatDecisions($alert['decisions'] ?? []),
|
||||
'created' => $alert['created_at'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\Util;
|
||||
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class AppsecconfigsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the installed appsec-configs
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec appsec-configs-list")), true);
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
|
||||
$items = $result["appsec-configs"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($items as $item) {
|
||||
$rows[] = [
|
||||
'name' => $item['name'],
|
||||
'status' => $item['status'] ?? '',
|
||||
'local_version' => $item['local_version'] ?? '',
|
||||
'local_path' => Util::trimLocalPath($item['local_path'] ?? ''),
|
||||
'description' => $item['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\Util;
|
||||
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class AppsecrulesController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the installed appsec-rules
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec appsec-rules-list")), true);
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
|
||||
$items = $result["appsec-rules"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($items as $item) {
|
||||
$rows[] = [
|
||||
'name' => $item['name'],
|
||||
'status' => $item['status'] ?? '',
|
||||
'local_version' => $item['local_version'] ?? '',
|
||||
'local_path' => Util::trimLocalPath($item['local_path'] ?? ''),
|
||||
'description' => $item['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\CrowdSec;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
@ -21,13 +20,27 @@ class BouncersController extends ApiControllerBase
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec bouncers-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $result;
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
return ["message" => "unable to list bouncers"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($result as $bouncer) {
|
||||
$rows[] = [
|
||||
'name' => $bouncer['name'],
|
||||
'type' => $bouncer['type'] ?? '',
|
||||
'version' => $bouncer['version'] ?? '',
|
||||
'created' => $bouncer['created_at'] ?? '',
|
||||
'valid' => ($bouncer['revoked'] ?? false) !== true,
|
||||
'ip_address' => $bouncer['ip_address'] ?? '',
|
||||
'last_seen' => $bouncer['last_pull'] ?? '',
|
||||
'os' => $bouncer['os'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\Util;
|
||||
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class CollectionsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the installed collections
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec collections-list")), true);
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
|
||||
$items = $result["collections"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($items as $item) {
|
||||
$rows[] = [
|
||||
'name' => $item['name'],
|
||||
'status' => $item['status'] ?? '',
|
||||
'local_version' => $item['local_version'] ?? '',
|
||||
'local_path' => Util::trimLocalPath($item['local_path'] ?? ''),
|
||||
'description' => $item['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
@ -6,14 +6,62 @@
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\CrowdSec;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
|
||||
function unrollDecisions(array $alerts): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($alerts as $alert) {
|
||||
if (!isset($alert['decisions']) || !is_array($alert['decisions'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($alert['decisions'] as $decision) {
|
||||
// ignore deleted decisions
|
||||
if (isset($decision['duration']) && str_starts_with($decision['duration'], '-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = $decision;
|
||||
|
||||
// Add parent alert fields with prefix
|
||||
foreach ($alert as $key => $value) {
|
||||
if ($key === 'decisions') {
|
||||
continue; // skip nested array
|
||||
}
|
||||
$row["alert_" . $key] = $value;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class DecisionsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Format scope and value as "scope:value"
|
||||
*
|
||||
* @param array $source Array with 'scope' and 'value' keys
|
||||
* @return string Formatted string
|
||||
*/
|
||||
private function formatScopeValue(array $source): string
|
||||
{
|
||||
$scope = $source['scope'] ?? '';
|
||||
if ($source['value'] !== '') {
|
||||
$scope = $scope . ':' . $source['value'];
|
||||
}
|
||||
return $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve list of decisions
|
||||
*
|
||||
@ -21,29 +69,50 @@ class DecisionsController extends ApiControllerBase
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec decisions-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $result;
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
return ["message" => "unable to list decisions"];
|
||||
|
||||
$decisions = unrollDecisions($result);
|
||||
|
||||
$rows = [];
|
||||
foreach ($decisions as $dec) {
|
||||
$alert_source = $dec['alert_source'] ?? [];
|
||||
|
||||
$rows[] = [
|
||||
'id' => $dec['id'],
|
||||
'source' => $dec['origin'] ?? '',
|
||||
'scope_value' => $this->formatScopeValue($dec),
|
||||
'reason' => $dec['scenario'] ?? '',
|
||||
'action' => $dec['type'] ?? '',
|
||||
'country' => $alert_source['cn'] ?? '',
|
||||
'as' => $alert_source['as_name'] ?? '',
|
||||
'events_count' => $dec['alert_events_count'] ?? '',
|
||||
'expiration' => $dec['duration'] ?? '',
|
||||
'alert_id' => $dec['alert_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
|
||||
public function deleteAction($decision_id)
|
||||
public function delAction($decision_id): array
|
||||
{
|
||||
if ($this->request->isDelete()) {
|
||||
$result = (new Backend())->configdRun("crowdsec decisions-delete ${decision_id}");
|
||||
if ($result !== null) {
|
||||
// why does the action return \n\n for empty output?
|
||||
if (trim($result) === '') {
|
||||
return ["message" => "OK"];
|
||||
}
|
||||
// TODO handle error
|
||||
return ["message" => result];
|
||||
if ($this->request->isPost()) {
|
||||
$result = (new Backend())->configdRun("crowdsec decisions-delete {$decision_id}");
|
||||
if ($result === null) {
|
||||
return ["result" => "deleted"];
|
||||
}
|
||||
return ["message" => "OK"];
|
||||
|
||||
// why does the action return \n\n for empty output?
|
||||
if (trim($result) === '') {
|
||||
return ["result" => "deleted"];
|
||||
}
|
||||
// TODO assume not found, should handle other errors
|
||||
return ["result" => "not found"];
|
||||
} else {
|
||||
$this->response->setStatusCode(405, "Method Not Allowed");
|
||||
$this->response->setHeader("Allow", "DELETE");
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\CrowdSec;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class HubController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the registered hub items
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec hub-items")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $result;
|
||||
}
|
||||
return ["message" => "unable to list hub items"];
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\CrowdSec;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
@ -15,19 +14,32 @@ use OPNsense\Core\Backend;
|
||||
class MachinesController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve list of registered machines
|
||||
* Retrieve list of machines
|
||||
*
|
||||
* @return array of machines
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec machines-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $result;
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
return ["message" => "unable to list machines"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($result as $machine) {
|
||||
$rows[] = [
|
||||
'name' => $machine['machineId'],
|
||||
'ip_address' => $machine['ipAddress'] ?? '',
|
||||
'version' => $machine['version'] ?? '',
|
||||
'validated' => $machine['isValidated'] ?? false,
|
||||
'created' => $machine['created_at'] ?? '',
|
||||
'last_seen' => $machine['last_heartbeat'] ?? '',
|
||||
'os' => $machine['os'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\Util;
|
||||
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class ParsersController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the installed parsers
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec parsers-list")), true);
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
|
||||
$items = $result["parsers"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($items as $item) {
|
||||
$rows[] = [
|
||||
'name' => $item['name'],
|
||||
'status' => $item['status'] ?? '',
|
||||
'local_version' => $item['local_version'] ?? '',
|
||||
'local_path' => Util::trimLocalPath($item['local_path'] ?? ''),
|
||||
'description' => $item['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\Util;
|
||||
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class PostoverflowsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the installed postoverflows
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec postoverflows-list")), true);
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
|
||||
$items = $result["postoverflows"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($items as $item) {
|
||||
$rows[] = [
|
||||
'name' => $item['name'],
|
||||
'status' => $item['status'] ?? '',
|
||||
'local_version' => $item['local_version'] ?? '',
|
||||
'local_path' => Util::trimLocalPath($item['local_path'] ?? ''),
|
||||
'description' => $item['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\Util;
|
||||
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class ScenariosController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Retrieve the installed scenarios
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function searchAction(): array
|
||||
{
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec scenarios-list")), true);
|
||||
if ($result === null) {
|
||||
return ["message" => "unable to retrieve data"];
|
||||
}
|
||||
|
||||
$items = $result["scenarios"];
|
||||
|
||||
$rows = [];
|
||||
foreach ($items as $item) {
|
||||
$rows[] = [
|
||||
'name' => $item['name'],
|
||||
'status' => $item['status'] ?? '',
|
||||
'local_version' => $item['local_version'] ?? '',
|
||||
'local_path' => Util::trimLocalPath($item['local_path'] ?? ''),
|
||||
'description' => $item['description'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->searchRecordsetBase($rows);
|
||||
}
|
||||
}
|
||||
@ -16,8 +16,10 @@ class ServiceController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* reconfigure CrowdSec
|
||||
*
|
||||
* @return array Status result
|
||||
*/
|
||||
public function reloadAction()
|
||||
public function reloadAction(): array
|
||||
{
|
||||
$status = "failed";
|
||||
if ($this->request->isPost()) {
|
||||
@ -36,7 +38,11 @@ class ServiceController extends ApiControllerBase
|
||||
/**
|
||||
* Retrieve status of crowdsec
|
||||
*
|
||||
* @return array
|
||||
* @return array{
|
||||
* status: string,
|
||||
* crowdsec-status: string,
|
||||
* crowdsec-firewall-status: string
|
||||
* }
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function statusAction()
|
||||
@ -44,24 +50,30 @@ class ServiceController extends ApiControllerBase
|
||||
$backend = new Backend();
|
||||
$response = $backend->configdRun("crowdsec crowdsec-status");
|
||||
|
||||
$status = "unknown";
|
||||
if (strpos($response, "not running") > 0) {
|
||||
$status = "stopped";
|
||||
} elseif (strpos($response, "is running") > 0) {
|
||||
$status = "running";
|
||||
$crowdsec_status = "unknown";
|
||||
if (strpos($response, "not running") !== false) {
|
||||
$crowdsec_status = "stopped";
|
||||
} elseif (strpos($response, "is running") !== false) {
|
||||
$crowdsec_status = "running";
|
||||
}
|
||||
|
||||
$response = $backend->configdRun("crowdsec crowdsec-firewall-status");
|
||||
|
||||
$firewall_status = "unknown";
|
||||
if (strpos($response, "not running") > 0) {
|
||||
if (strpos($response, "not running") !== false) {
|
||||
$firewall_status = "stopped";
|
||||
} elseif (strpos($response, "is running") > 0) {
|
||||
} elseif (strpos($response, "is running") !== false) {
|
||||
$firewall_status = "running";
|
||||
}
|
||||
|
||||
$status = "unknown";
|
||||
if ($crowdsec_status == $firewall_status) {
|
||||
$status = $crowdsec_status;
|
||||
}
|
||||
|
||||
return [
|
||||
"crowdsec-status" => $status,
|
||||
"status" => $status,
|
||||
"crowdsec-status" => $crowdsec_status,
|
||||
"crowdsec-firewall-status" => $firewall_status,
|
||||
];
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
namespace OPNsense\CrowdSec\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\CrowdSec\CrowdSec;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
@ -21,7 +20,7 @@ class VersionController extends ApiControllerBase
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
public function getAction(): string
|
||||
{
|
||||
return (new Backend())->configdRun("crowdsec version");
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class AppsecconfigsController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class AppsecconfigsController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/appsecconfigs');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class AppsecrulesController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class AppsecrulesController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/appsecrules');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class BouncersController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class BouncersController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/bouncers');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class CollectionsController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class CollectionsController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/collections');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class DecisionsController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class DecisionsController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/decisions');
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,7 @@ namespace OPNsense\CrowdSec;
|
||||
*/
|
||||
class GeneralController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/general');
|
||||
$this->view->generalForm = $this->getForm("general");
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class MachinesController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class MachinesController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/machines');
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,7 @@ namespace OPNsense\CrowdSec;
|
||||
*/
|
||||
class OverviewController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/overview');
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class ParsersController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class ParsersController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/parsers');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class PostoverflowsController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class PostoverflowsController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/postoverflows');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net>
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
/**
|
||||
* Class ScenariosController
|
||||
* @package OPNsense\CrowdSec
|
||||
*/
|
||||
class ScenariosController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->pick('OPNsense/CrowdSec/scenarios');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace OPNsense\CrowdSec;
|
||||
|
||||
class Util
|
||||
{
|
||||
public static function trimLocalPath($local_path): string
|
||||
{
|
||||
$prefix = '/usr/local/etc/crowdsec/';
|
||||
if (str_starts_with($local_path, $prefix)) {
|
||||
return substr($local_path, strlen($prefix));
|
||||
}
|
||||
return $local_path;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
<model>
|
||||
<mount>//OPNsense/crowdsec/general</mount>
|
||||
<description>CrowdSec general configuration</description>
|
||||
<version>1.0.10</version>
|
||||
<version>1.0.11</version>
|
||||
<items>
|
||||
|
||||
<agent_enabled type="BooleanField">
|
||||
|
||||
@ -2,7 +2,16 @@
|
||||
<Services>
|
||||
<CrowdSec cssClass="fa fa-globe fa-fw">
|
||||
<Settings order="1" url="/ui/crowdsec/general/index"/>
|
||||
<Overview order="2" url="/ui/crowdsec/overview"/>
|
||||
<Machines order="2" url="/ui/crowdsec/machines/index"/>
|
||||
<Bouncers order="3" url="/ui/crowdsec/bouncers/index"/>
|
||||
<Alerts order="5" url="/ui/crowdsec/alerts/index"/>
|
||||
<Decisions order="6" url="/ui/crowdsec/decisions/index"/>
|
||||
<Collections order="7" url="/ui/crowdsec/collections/index"/>
|
||||
<Scenarios order="8" url="/ui/crowdsec/scenarios/index"/>
|
||||
<Parsers order="9" url="/ui/crowdsec/parsers/index"/>
|
||||
<Postoverflows order="10" url="/ui/crowdsec/postoverflows/index"/>
|
||||
<Appsecrules order="11" url="/ui/crowdsec/appsecrules/index"/>
|
||||
<Appsecconfigs order="12" url="/ui/crowdsec/appsecconfigs/index"/>
|
||||
</CrowdSec>
|
||||
</Services>
|
||||
</menu>
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
const decisionsByType = function(decisions) {
|
||||
const dectypes = {};
|
||||
if (!decisions) {
|
||||
return '';
|
||||
}
|
||||
decisions.map(function (decision) {
|
||||
// TODO ignore negative expiration?
|
||||
dectypes[decision.type] = dectypes[decision.type]
|
||||
? dectypes[decision.type] + 1
|
||||
: 1;
|
||||
});
|
||||
let ret = '';
|
||||
for (const type in dectypes) {
|
||||
if (ret !== '') {
|
||||
ret += ' ';
|
||||
}
|
||||
ret += type + ':' + dectypes[type];
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
$("#cscli_alerts").UIBootgrid({
|
||||
search: '/api/crowdsec/alerts/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
formatters: {
|
||||
"created": CrowdSec.formatters.datetime,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_alerts" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="id" data-type="numeric" data-order="asc">ID</th>
|
||||
<th data-column-id="value">Value</th>
|
||||
<th data-column-id="reason">Reason</th>
|
||||
<th data-column-id="country">Country</th>
|
||||
<th data-column-id="as">AS</th>
|
||||
<th data-column-id="decisions">Decisions</th>
|
||||
<th data-column-id="created_at" data-formatter="created">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,36 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_appsecconfigs").UIBootgrid({
|
||||
search: '/api/crowdsec/appsecconfigs/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_appsecconfigs" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-column-id="local_path" data-formatter="localpath" data-visible="false">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,36 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_appsecrules").UIBootgrid({
|
||||
search: '/api/crowdsec/appsecrules/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_appsecrules" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-column-id="local_path" data-formatter="localpath" data-visible="false">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,44 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_bouncers").UIBootgrid({
|
||||
search: '/api/crowdsec/bouncers/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
formatters: {
|
||||
"created": CrowdSec.formatters.datetime,
|
||||
"last_seen": CrowdSec.formatters.datetime,
|
||||
"valid": CrowdSec.formatters.yesno,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_bouncers" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="type">Type</th>
|
||||
<th data-column-id="version">Version</th>
|
||||
<th data-column-id="created" data-formatter="created" data-visible="false">Created</th>
|
||||
<th data-column-id="valid" data-formatter="valid">Valid</th>
|
||||
<th data-column-id="ip_address">IP Address</th>
|
||||
<th data-column-id="last_seen" data-formatter="last_seen">Last Seen</th>
|
||||
<th data-column-id="os" data-visible="false">OS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,36 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_collections").UIBootgrid({
|
||||
search: '/api/crowdsec/collections/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_collections" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-column-id="local_path" data-formatter="localpath" data-visible="false">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,51 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_decisions").UIBootgrid({
|
||||
search: '/api/crowdsec/decisions/search/',
|
||||
del: '/api/crowdsec/decisions/del/',
|
||||
datakey: "id",
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
Note: the decisions coming from the CAPI (signals collected by the CrowdSec users) do not appear here.
|
||||
To show them, use <code>cscli decisions list -a</code> in a shell.
|
||||
|
||||
<table id="cscli_decisions" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="id" data-type="numeric" data-visible="false" data-order="asc">ID</th>
|
||||
<th data-column-id="source" data-visible="false">Source</th>
|
||||
<th data-column-id="scope_value">Scope:Value</th>
|
||||
<th data-column-id="reason">Reason</th>
|
||||
<th data-column-id="action" data-visible="false">Action</th>
|
||||
<th data-column-id="country">Country</th>
|
||||
<th data-column-id="as">AS</th>
|
||||
<th data-column-id="events_count" data-type="numeric">Events</th>
|
||||
<th data-column-id="expiration">Expiration</th>
|
||||
<th data-column-id="alert_id" data-type="numeric" data-visible="false">Alert ID</th>
|
||||
<th data-column-id="commands" data-formatter="commands" data-sortable="false">Commands</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td/>
|
||||
<td>
|
||||
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default">
|
||||
<span class="fa fa-trash-o fa-fw"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,43 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_machines").UIBootgrid({
|
||||
search: '/api/crowdsec/machines/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
formatters: {
|
||||
"created": CrowdSec.formatters.datetime,
|
||||
"last_seen": CrowdSec.formatters.datetime,
|
||||
"validated": CrowdSec.formatters.yesno,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_machines" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="version">Version</th>
|
||||
<th data-column-id="validated" data-formatter="validated">Validated?</th>
|
||||
<th data-column-id="ip_address">IP Address</th>
|
||||
<th data-column-id="created" data-formatter="created" data-visible="false">Created</th>
|
||||
<th data-column-id="last_seen" data-formatter="last_seen">Last Seen</th>
|
||||
<th data-column-id="os" data-visible="false">OS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -1,246 +0,0 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec.js"></script>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
CrowdSec.init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.content-box table {
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
table.bootgrid-table tr .btn-sm {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
table.bootgrid-table tr > td {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
li.spaced {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
ul.nav>li>a {
|
||||
padding: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
Service status: crowdsec <span id="crowdsec-status">...</span> - firewall bouncer <span id="crowdsec-firewall-status">...</span>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li><a data-toggle="tab" id="machines_tab" href="#machines">Machines</a></li>
|
||||
<li><a data-toggle="tab" id="bouncers_tab" href="#bouncers">Bouncers</a></li>
|
||||
<li class="spaced"><a data-toggle="tab" id="collections_tab" href="#collections">Collections</a></li>
|
||||
<li><a data-toggle="tab" id="scenarios_tab" href="#scenarios">Scenarios</a></li>
|
||||
<li><a data-toggle="tab" id="parsers_tab" href="#parsers">Parsers</a></li>
|
||||
<li><a data-toggle="tab" id="postoverflows_tab" href="#postoverflows">Postoverflows</a></li>
|
||||
<li class="spaced"><a data-toggle="tab" id="alerts_tab" href="#alerts">Alerts</a></li>
|
||||
<li><a data-toggle="tab" id="decisions_tab" href="#decisions">Decisions</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box">
|
||||
|
||||
<div id="machines" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name" data-order="asc">Name</th>
|
||||
<th data-column-id="ip_address">IP Address</th>
|
||||
<th data-column-id="last_update" data-formatter="datetime">Last Update</th>
|
||||
<th data-column-id="validated" data-formatter="yesno" data-searchable="false">Validated?</th>
|
||||
<th data-column-id="version">Version</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="bouncers" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name" data-order="asc">Name</th>
|
||||
<th data-column-id="ip_address">IP Address</th>
|
||||
<th data-column-id="valid" data-formatter="yesno" data-searchable="false">Valid</th>
|
||||
<th data-column-id="last_pull" data-formatter="datetime">Last API Pull</th>
|
||||
<th data-column-id="type">Type</th>
|
||||
<th data-column-id="version">Version</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="collections" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name" data-order="asc">Collection</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-visible="false" data-column-id="local_path">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="scenarios" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name" data-order="asc">Scenario</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-visible="false" data-column-id="local_path">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="parsers" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name" data-order="asc">Parser</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-visible="false" data-column-id="local_path">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="postoverflows" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name" data-order="asc">Postoverflow</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-visible="false" data-column-id="local_path">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="alerts" class="tab-pane fade in">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="id" data-type="numeric" data-order="asc">ID</th>
|
||||
<th data-column-id="value">Value</th>
|
||||
<th data-column-id="reason">Reason</th>
|
||||
<th data-column-id="country">Country</th>
|
||||
<th data-column-id="as">AS</th>
|
||||
<th data-column-id="decisions">Decisions</th>
|
||||
<th data-column-id="created_at" data-formatter="datetime">Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="decisions" class="tab-pane fade in">
|
||||
Note: the decisions coming from the CAPI (signals collected by the CrowdSec users) do not appear here.
|
||||
To show them, use <code>cscli decisions list -a</code> in a shell.
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="delete" data-formatter="delete"
|
||||
data-visible-in-selection="false"></th>
|
||||
<th data-column-id="id" data-visible="false" data-identifier="true" data-type="numeric"
|
||||
data-order="asc">ID</th>
|
||||
<th data-visible="false" data-column-id="source">Source</th>
|
||||
<th data-column-id="scope_value">Scope:Value</th>
|
||||
<th data-column-id="reason">Reason</th>
|
||||
<th data-visible="false" data-column-id="action">Action</th>
|
||||
<th data-column-id="country">Country</th>
|
||||
<th data-column-id="as">AS</th>
|
||||
<th data-column-id="events_count" data-type="numeric">Events</th>
|
||||
<th data-column-id="expiration" data-formatter="duration">Expiration</th>
|
||||
<th data-visible="false" data-column-id="alert_id" data-type="numeric">Alert ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Modal popup to confirm decision deletion -->
|
||||
<div class="modal fade" id="remove-decision-modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="modalLabel">Modal Title</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Modal content...
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">No, cancel</button>
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal" id="remove-decision-confirm">Yes, delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -0,0 +1,36 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_parsers").UIBootgrid({
|
||||
search: '/api/crowdsec/parsers/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_parsers" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-column-id="local_path" data-formatter="localpath" data-visible="false">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,36 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_postoverflows").UIBootgrid({
|
||||
search: '/api/crowdsec/postoverflows/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_postoverflows" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-column-id="local_path" data-formatter="localpath" data-visible="false">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -0,0 +1,36 @@
|
||||
{# SPDX-License-Identifier: MIT #}
|
||||
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}
|
||||
|
||||
<script src="/ui/js/moment-with-locales.min.js"></script>
|
||||
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
$(function() {
|
||||
$("#cscli_scenarios").UIBootgrid({
|
||||
search: '/api/crowdsec/scenarios/search/',
|
||||
options: {
|
||||
selection: false,
|
||||
multiSelect: false,
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('crowdsec');
|
||||
});
|
||||
</script>
|
||||
|
||||
<table id="cscli_scenarios" class="table table-condensed table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="name">Name</th>
|
||||
<th data-column-id="status">Status</th>
|
||||
<th data-column-id="local_version">Version</th>
|
||||
<th data-column-id="local_path" data-formatter="localpath" data-visible="false">Path</th>
|
||||
<th data-column-id="description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -57,10 +57,35 @@ parameters:--id %s
|
||||
type:script_output
|
||||
message:crowdsec decisions delete
|
||||
|
||||
[hub-items]
|
||||
command:/usr/local/bin/cscli hub list -o json
|
||||
[collections-list]
|
||||
command:/usr/local/bin/cscli collections list -o json
|
||||
type:script_output
|
||||
message:crowdsec hub list
|
||||
message:crowdsec collections list
|
||||
|
||||
[scenarios-list]
|
||||
command:/usr/local/bin/cscli scenarios list -o json
|
||||
type:script_output
|
||||
message:crowdsec scenarios list
|
||||
|
||||
[parsers-list]
|
||||
command:/usr/local/bin/cscli parsers list -o json
|
||||
type:script_output
|
||||
message:crowdsec parsers list
|
||||
|
||||
[postoverflows-list]
|
||||
command:/usr/local/bin/cscli postoverflows list -o json
|
||||
type:script_output
|
||||
message:crowdsec postoverflows list
|
||||
|
||||
[appsec-rules-list]
|
||||
command:/usr/local/bin/cscli appsec-rules list -o json
|
||||
type:script_output
|
||||
message:crowdsec appsec-rules list
|
||||
|
||||
[appsec-configs-list]
|
||||
command:/usr/local/bin/cscli appsec-configs list -o json
|
||||
type:script_output
|
||||
message:crowdsec appsec-configs list
|
||||
|
||||
[machines-list]
|
||||
command:/usr/local/bin/cscli machines list -o json
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
/* global moment, $ */
|
||||
/* exported CrowdSec */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint semi: "error" */
|
||||
|
||||
const CrowdSec = (function () {
|
||||
'use strict';
|
||||
|
||||
function _humanizeDate(text) {
|
||||
return moment(text).fromNow();
|
||||
}
|
||||
|
||||
const formatters = {
|
||||
yesno: function(column, row) {
|
||||
const val = row[column.id];
|
||||
if (val) {
|
||||
return '<i class="fa fa-check text-success"></i>';
|
||||
} else {
|
||||
return '<i class="fa fa-times text-danger"></i>';
|
||||
}
|
||||
},
|
||||
|
||||
datetime: function (column, row) {
|
||||
const val = row[column.id];
|
||||
const parsed = moment(val);
|
||||
if (!val) {
|
||||
return '';
|
||||
}
|
||||
if (!parsed.isValid()) {
|
||||
console.error('Cannot parse timestamp: %s', val);
|
||||
return '???';
|
||||
}
|
||||
return $('<div>')
|
||||
.attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'data-placement': 'left',
|
||||
title: parsed.format(),
|
||||
})
|
||||
.text(_humanizeDate(val))
|
||||
.prop('outerHTML');
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
formatters: formatters,
|
||||
};
|
||||
})();
|
||||
@ -1,473 +0,0 @@
|
||||
/* global moment, $ */
|
||||
/* exported CrowdSec */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint semi: "error" */
|
||||
|
||||
const CrowdSec = (function () {
|
||||
'use strict';
|
||||
|
||||
const crowdsec_path = '/usr/local/etc/crowdsec/';
|
||||
const _refreshTemplate =
|
||||
'<button class="btn btn-default" type="button" title="Refresh"><span class="icon fa fa-refresh"></span></button>';
|
||||
|
||||
const _dataFormatters = {
|
||||
yesno: function (column, row) {
|
||||
return _yesno2html(row[column.id]);
|
||||
},
|
||||
|
||||
delete: function (column, row) {
|
||||
const val = row.id;
|
||||
if (isNaN(val)) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
'<button type="button" class="btn btn-secondary btn-sm" value="' +
|
||||
val +
|
||||
'" onclick="CrowdSec.deleteDecision(' +
|
||||
val +
|
||||
')"><i class="fa fa-trash" /></button>'
|
||||
);
|
||||
},
|
||||
|
||||
duration: function (column, row) {
|
||||
const duration = row[column.id];
|
||||
if (!duration) {
|
||||
return 'n/a';
|
||||
}
|
||||
return $('<div>')
|
||||
.attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'data-placement': 'left',
|
||||
title: duration,
|
||||
})
|
||||
.text(_humanizeDuration(duration))
|
||||
.prop('outerHTML');
|
||||
},
|
||||
|
||||
datetime: function (column, row) {
|
||||
const dt = row[column.id];
|
||||
const parsed = moment(dt);
|
||||
if (!dt) {
|
||||
return '';
|
||||
}
|
||||
if (!parsed.isValid()) {
|
||||
console.error('Cannot parse timestamp: %s', dt);
|
||||
return '???';
|
||||
}
|
||||
return $('<div>')
|
||||
.attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'data-placement': 'left',
|
||||
title: parsed.format(),
|
||||
})
|
||||
.text(_humanizeDate(dt))
|
||||
.prop('outerHTML');
|
||||
},
|
||||
};
|
||||
function _decisionsByType(decisions) {
|
||||
const dectypes = {};
|
||||
if (!decisions) {
|
||||
return '';
|
||||
}
|
||||
decisions.map(function (decision) {
|
||||
// TODO ignore negative expiration?
|
||||
dectypes[decision.type] = dectypes[decision.type]
|
||||
? dectypes[decision.type] + 1
|
||||
: 1;
|
||||
});
|
||||
let ret = '';
|
||||
for (const type in dectypes) {
|
||||
if (ret !== '') {
|
||||
ret += ' ';
|
||||
}
|
||||
ret += type + ':' + dectypes[type];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function _updateFreshness(selector, timestamp) {
|
||||
const $freshness = $(selector).find('.actionBar .freshness');
|
||||
if (timestamp) {
|
||||
$freshness.data('refresh_timestamp', timestamp);
|
||||
} else {
|
||||
timestamp = $freshness.data('refresh_timestamp');
|
||||
}
|
||||
const howlongHuman = '???';
|
||||
if (timestamp) {
|
||||
const howlongms = moment() - moment(timestamp);
|
||||
howlongHuman = moment.duration(howlongms).humanize();
|
||||
}
|
||||
$freshness.text(howlongHuman + ' ago');
|
||||
}
|
||||
|
||||
function _addFreshness(selector) {
|
||||
// this creates one timer per tab
|
||||
const freshnessTemplate =
|
||||
'<span style="float:left">Last refresh: <span class="freshness"></span></span>';
|
||||
$(selector).find('.actionBar').prepend(freshnessTemplate);
|
||||
setInterval(function () {
|
||||
_updateFreshness(selector);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function _refreshTab(selector, url, dataCallback) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
cache: false,
|
||||
success: dataCallback,
|
||||
complete: function () {
|
||||
_updateFreshness(selector, moment());
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function _parseDuration(duration) {
|
||||
const re = /(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m;
|
||||
const matches = duration.match(re);
|
||||
let seconds = 0;
|
||||
|
||||
if (!matches.length) {
|
||||
throw new Error(
|
||||
'Unable to parse the following duration: ' + duration + '.',
|
||||
);
|
||||
}
|
||||
if (typeof matches[2] !== 'undefined') {
|
||||
seconds += parseInt(matches[2], 10) * 3600; // hours
|
||||
}
|
||||
if (typeof matches[3] !== 'undefined') {
|
||||
seconds += parseInt(matches[3], 10) * 60; // minutes
|
||||
}
|
||||
if (typeof matches[4] !== 'undefined') {
|
||||
seconds += parseInt(matches[4], 10); // seconds
|
||||
}
|
||||
if (parseInt(matches[5], 10) === 'm') {
|
||||
// units in milliseconds
|
||||
seconds *= 0.001;
|
||||
}
|
||||
if (parseInt(matches[1], 10) === '-') {
|
||||
// negative
|
||||
seconds = -seconds;
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
function _humanizeDate(text) {
|
||||
return moment(text).fromNow();
|
||||
}
|
||||
|
||||
function _humanizeDuration(text) {
|
||||
return moment.duration(_parseDuration(text), 'seconds').humanize();
|
||||
}
|
||||
|
||||
function _yesno2html(val) {
|
||||
if (val) {
|
||||
return '<i class="fa fa-check text-success"></i>';
|
||||
} else {
|
||||
return '<i class="fa fa-times text-danger"></i>';
|
||||
}
|
||||
}
|
||||
|
||||
function _initTab(selector, url, dataCallback) {
|
||||
const $tab = $(selector);
|
||||
if ($tab.find('table.bootgrid-table').length) {
|
||||
return;
|
||||
}
|
||||
$tab
|
||||
.find('table')
|
||||
.on('initialized.rs.jquery.bootgrid', function () {
|
||||
$(_refreshTemplate)
|
||||
.on('click', function () {
|
||||
_refreshTab(selector, url, dataCallback);
|
||||
})
|
||||
.insertBefore($tab.find('.actionBar .actions .dropdown:first'));
|
||||
_addFreshness(selector);
|
||||
_refreshTab(selector, url, dataCallback);
|
||||
})
|
||||
.bootgrid({
|
||||
caseSensitive: false,
|
||||
formatters: _dataFormatters,
|
||||
});
|
||||
}
|
||||
|
||||
function _initStatusMachines() {
|
||||
const url = '/api/crowdsec/machines/get';
|
||||
const id = '#machines';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.map(function (row) {
|
||||
rows.push({
|
||||
name: row.machineId,
|
||||
ip_address: row.ipAddress || ' ',
|
||||
last_update: row.updated_at || ' ',
|
||||
validated: row.isValidated,
|
||||
version: row.version || ' ',
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusCollections() {
|
||||
const url = '/api/crowdsec/hub/get';
|
||||
const id = '#collections';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.collections.map(function (row) {
|
||||
rows.push({
|
||||
name: row.name,
|
||||
status: row.status,
|
||||
local_version: row.local_version || ' ',
|
||||
local_path: row.local_path
|
||||
? row.local_path.replace(crowdsec_path, '')
|
||||
: ' ',
|
||||
description: row.description || ' ',
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusScenarios() {
|
||||
const url = '/api/crowdsec/hub/get';
|
||||
const id = '#scenarios';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.scenarios.map(function (row) {
|
||||
rows.push({
|
||||
name: row.name,
|
||||
status: row.status,
|
||||
local_version: row.local_version || ' ',
|
||||
local_path: row.local_path
|
||||
? row.local_path.replace(crowdsec_path, '')
|
||||
: ' ',
|
||||
description: row.description || ' ',
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusParsers() {
|
||||
const url = '/api/crowdsec/hub/get';
|
||||
const id = '#parsers';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.parsers.map(function (row) {
|
||||
rows.push({
|
||||
name: row.name,
|
||||
status: row.status,
|
||||
local_version: row.local_version || ' ',
|
||||
local_path: row.local_path
|
||||
? row.local_path.replace(crowdsec_path, '')
|
||||
: ' ',
|
||||
description: row.description || ' ',
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusPostoverflows() {
|
||||
const url = '/api/crowdsec/hub/get';
|
||||
const id = '#postoverflows';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.postoverflows.map(function (row) {
|
||||
rows.push({
|
||||
name: row.name,
|
||||
status: row.status,
|
||||
local_version: row.local_version || ' ',
|
||||
local_path: row.local_path
|
||||
? row.local_path.replace(crowdsec_path, '')
|
||||
: ' ',
|
||||
description: row.description || ' ',
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusBouncers() {
|
||||
const url = '/api/crowdsec/bouncers/get';
|
||||
const id = '#bouncers';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.map(function (row) {
|
||||
// TODO - remove || ' ' later, it was fixed for 1.3.3
|
||||
rows.push({
|
||||
name: row.name,
|
||||
ip_address: row.ip_address || ' ',
|
||||
valid: row.revoked ? false : true,
|
||||
last_pull: row.last_pull,
|
||||
type: row.type || ' ',
|
||||
version: row.version || ' ',
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusAlerts() {
|
||||
const url = '/api/crowdsec/alerts/get';
|
||||
const id = '#alerts';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.map(function (row) {
|
||||
rows.push({
|
||||
id: row.id,
|
||||
value:
|
||||
row.source.scope + (row.source.value ? ':' + row.source.value : ''),
|
||||
reason: row.scenario || ' ',
|
||||
country: row.source.cn || ' ',
|
||||
as: row.source.as_name || ' ',
|
||||
decisions: _decisionsByType(row.decisions) || ' ',
|
||||
created_at: row.created_at,
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function _initStatusDecisions() {
|
||||
const url = '/api/crowdsec/decisions/get';
|
||||
const id = '#decisions';
|
||||
const dataCallback = function (data) {
|
||||
const rows = [];
|
||||
data.map(function (row) {
|
||||
row.decisions.map(function (decision) {
|
||||
// ignore deleted decisions
|
||||
if (decision.duration.startsWith('-')) {
|
||||
return;
|
||||
}
|
||||
rows.push({
|
||||
// search will break on empty values when using .append(). so we use spaces
|
||||
delete: '',
|
||||
id: decision.id,
|
||||
source: decision.origin || ' ',
|
||||
scope_value:
|
||||
decision.scope + (decision.value ? ':' + decision.value : ''),
|
||||
reason: decision.scenario || ' ',
|
||||
action: decision.type || ' ',
|
||||
country: row.source.cn || ' ',
|
||||
as: row.source.as_name || ' ',
|
||||
events_count: row.events_count,
|
||||
// XXX pre-parse duration to seconds, and integer type, for sorting
|
||||
expiration: decision.duration || ' ',
|
||||
alert_id: row.id || ' ',
|
||||
});
|
||||
});
|
||||
});
|
||||
$(id + ' table')
|
||||
.bootgrid('clear')
|
||||
.bootgrid('append', rows);
|
||||
};
|
||||
_initTab(id, url, dataCallback);
|
||||
}
|
||||
|
||||
function initService() {
|
||||
$.ajax({
|
||||
url: '/api/crowdsec/service/status',
|
||||
cache: false,
|
||||
success: function (data) {
|
||||
let crowdsecStatus = data['crowdsec-status'];
|
||||
if (crowdsecStatus === 'unknown') {
|
||||
crowdsecStatus = '<span class="text-danger">Unknown</span>';
|
||||
} else {
|
||||
crowdsecStatus = _yesno2html(crowdsecStatus === 'running');
|
||||
}
|
||||
$('#crowdsec-status').html(crowdsecStatus);
|
||||
|
||||
let crowdsecFirewallStatus = data['crowdsec-firewall-status'];
|
||||
if (crowdsecFirewallStatus === 'unknown') {
|
||||
crowdsecFirewallStatus = '<span class="text-danger">Unknown</span>';
|
||||
} else {
|
||||
crowdsecFirewallStatus = _yesno2html(
|
||||
crowdsecFirewallStatus === 'running',
|
||||
);
|
||||
}
|
||||
$('#crowdsec-firewall-status').html(crowdsecFirewallStatus);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function deleteDecision(decisionId) {
|
||||
const $modal = $('#remove-decision-modal');
|
||||
$modal.find('.modal-title').text('Delete decision #' + decisionId);
|
||||
$modal.find('.modal-body').text('Are you sure?');
|
||||
$modal.find('#remove-decision-confirm').on('click', function () {
|
||||
$.ajax({
|
||||
// XXX handle errors
|
||||
url: '/api/crowdsec/decisions/delete/' + decisionId,
|
||||
method: 'DELETE',
|
||||
success: function (result) {
|
||||
if (result && result.message === 'OK') {
|
||||
$('#decisions table').bootgrid('remove', [decisionId]);
|
||||
$modal.modal('hide');
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
$modal.modal('show');
|
||||
}
|
||||
|
||||
function init() {
|
||||
initService();
|
||||
|
||||
$('#machines_tab').on('click', _initStatusMachines);
|
||||
$('#collections_tab').on('click', _initStatusCollections);
|
||||
$('#scenarios_tab').on('click', _initStatusScenarios);
|
||||
$('#parsers_tab').on('click', _initStatusParsers);
|
||||
$('#postoverflows_tab').on('click', _initStatusPostoverflows);
|
||||
$('#bouncers_tab').on('click', _initStatusBouncers);
|
||||
$('#alerts_tab').on('click', _initStatusAlerts);
|
||||
$('#decisions_tab').on('click', _initStatusDecisions);
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
if (window.location.hash) {
|
||||
// activate a tab from the hash, if it exists
|
||||
$(window.location.hash + '_tab').click();
|
||||
} else {
|
||||
// otherwise, machines
|
||||
$('#machines_tab').click();
|
||||
}
|
||||
|
||||
$(window).on('hashchange', function (e) {
|
||||
$(window.location.hash + '_tab').click();
|
||||
});
|
||||
|
||||
// navigation
|
||||
if (window.location.hash !== '') {
|
||||
$('a[href="' + window.location.hash + '"]').click();
|
||||
}
|
||||
$('.nav-tabs a').on('shown.bs.tab', function (e) {
|
||||
history.pushState(null, null, e.target.hash);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
deleteDecision: deleteDecision,
|
||||
init: init,
|
||||
};
|
||||
})();
|
||||
Loading…
x
Reference in New Issue
Block a user