mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-13 13:35:54 +00:00
Add statistic tracking
This commit is contained in:
@@ -8,13 +8,13 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$subdomain = $this->detectName();
|
$folderName = $this->detectName();
|
||||||
$host = $this->prepareSharedHost($subdomain.'.'.$this->detectTld());
|
$host = $this->prepareSharedHost($folderName.'.'.$this->detectTld());
|
||||||
|
|
||||||
$this->input->setArgument('host', $host);
|
$this->input->setArgument('host', $host);
|
||||||
|
|
||||||
if (! $this->option('subdomain')) {
|
if (! $this->option('subdomain')) {
|
||||||
$this->input->setOption('subdomain', $subdomain);
|
$this->input->setOption('subdomain', str_replace('.', '-', $folderName));
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::handle();
|
parent::handle();
|
||||||
@@ -56,7 +56,7 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return str_replace('.', '-', basename($projectPath));
|
return basename($projectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function detectProtocol($host): string
|
protected function detectProtocol($host): string
|
||||||
|
|||||||
18
app/Contracts/StatisticsCollector.php
Normal file
18
app/Contracts/StatisticsCollector.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
interface StatisticsCollector
|
||||||
|
{
|
||||||
|
public function siteShared($authToken = null);
|
||||||
|
|
||||||
|
public function portShared($authToken = null);
|
||||||
|
|
||||||
|
public function incomingRequest();
|
||||||
|
|
||||||
|
public function flush();
|
||||||
|
|
||||||
|
public function save();
|
||||||
|
|
||||||
|
public function shouldCollectStatistics(): bool;
|
||||||
|
}
|
||||||
10
app/Contracts/StatisticsRepository.php
Normal file
10
app/Contracts/StatisticsRepository.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
interface StatisticsRepository
|
||||||
|
{
|
||||||
|
public function getStatistics($from, $until): PromiseInterface;
|
||||||
|
}
|
||||||
@@ -19,4 +19,6 @@ interface UserRepository
|
|||||||
public function deleteUser($id): PromiseInterface;
|
public function deleteUser($id): PromiseInterface;
|
||||||
|
|
||||||
public function getUsersByTokens(array $authTokens): PromiseInterface;
|
public function getUsersByTokens(array $authTokens): PromiseInterface;
|
||||||
|
|
||||||
|
public function updateLastSharedAt($id): PromiseInterface;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Server\Connections;
|
namespace App\Server\Connections;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
||||||
|
use App\Contracts\StatisticsCollector;
|
||||||
use App\Contracts\SubdomainGenerator;
|
use App\Contracts\SubdomainGenerator;
|
||||||
use App\Http\QueryParameters;
|
use App\Http\QueryParameters;
|
||||||
use App\Server\Exceptions\NoFreePortAvailable;
|
use App\Server\Exceptions\NoFreePortAvailable;
|
||||||
@@ -25,10 +26,14 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
/** @var LoopInterface */
|
/** @var LoopInterface */
|
||||||
protected $loop;
|
protected $loop;
|
||||||
|
|
||||||
public function __construct(SubdomainGenerator $subdomainGenerator, LoopInterface $loop)
|
/** @var StatisticsCollector */
|
||||||
|
protected $statisticsCollector;
|
||||||
|
|
||||||
|
public function __construct(SubdomainGenerator $subdomainGenerator, StatisticsCollector $statisticsCollector, LoopInterface $loop)
|
||||||
{
|
{
|
||||||
$this->subdomainGenerator = $subdomainGenerator;
|
$this->subdomainGenerator = $subdomainGenerator;
|
||||||
$this->loop = $loop;
|
$this->loop = $loop;
|
||||||
|
$this->statisticsCollector = $statisticsCollector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function limitConnectionLength(ControlConnection $connection, int $maximumConnectionLength)
|
public function limitConnectionLength(ControlConnection $connection, int $maximumConnectionLength)
|
||||||
@@ -60,6 +65,8 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
|
|
||||||
$this->connections[] = $storedConnection;
|
$this->connections[] = $storedConnection;
|
||||||
|
|
||||||
|
$this->statisticsCollector->siteShared($this->getAuthTokenFromConnection($connection));
|
||||||
|
|
||||||
return $storedConnection;
|
return $storedConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +86,8 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
|
|
||||||
$this->connections[] = $storedConnection;
|
$this->connections[] = $storedConnection;
|
||||||
|
|
||||||
|
$this->statisticsCollector->portShared($this->getAuthTokenFromConnection($connection));
|
||||||
|
|
||||||
return $storedConnection;
|
return $storedConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Server;
|
namespace App\Server;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
||||||
|
use App\Contracts\StatisticsCollector;
|
||||||
|
use App\Contracts\StatisticsRepository;
|
||||||
use App\Contracts\SubdomainGenerator;
|
use App\Contracts\SubdomainGenerator;
|
||||||
use App\Contracts\SubdomainRepository;
|
use App\Contracts\SubdomainRepository;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
@@ -15,6 +17,7 @@ use App\Server\Http\Controllers\Admin\DisconnectSiteController;
|
|||||||
use App\Server\Http\Controllers\Admin\DisconnectTcpConnectionController;
|
use App\Server\Http\Controllers\Admin\DisconnectTcpConnectionController;
|
||||||
use App\Server\Http\Controllers\Admin\GetSettingsController;
|
use App\Server\Http\Controllers\Admin\GetSettingsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetSitesController;
|
use App\Server\Http\Controllers\Admin\GetSitesController;
|
||||||
|
use App\Server\Http\Controllers\Admin\GetStatisticsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetTcpConnectionsController;
|
use App\Server\Http\Controllers\Admin\GetTcpConnectionsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetUserDetailsController;
|
use App\Server\Http\Controllers\Admin\GetUserDetailsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetUsersController;
|
use App\Server\Http\Controllers\Admin\GetUsersController;
|
||||||
@@ -29,6 +32,8 @@ use App\Server\Http\Controllers\Admin\StoreUsersController;
|
|||||||
use App\Server\Http\Controllers\ControlMessageController;
|
use App\Server\Http\Controllers\ControlMessageController;
|
||||||
use App\Server\Http\Controllers\TunnelMessageController;
|
use App\Server\Http\Controllers\TunnelMessageController;
|
||||||
use App\Server\Http\Router;
|
use App\Server\Http\Router;
|
||||||
|
use App\Server\StatisticsCollector\DatabaseStatisticsCollector;
|
||||||
|
use App\Server\StatisticsRepository\DatabaseStatisticsRepository;
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
use Phar;
|
use Phar;
|
||||||
use Ratchet\Server\IoServer;
|
use Ratchet\Server\IoServer;
|
||||||
@@ -128,6 +133,7 @@ class Factory
|
|||||||
$this->router->get('/sites', ListSitesController::class, $adminCondition);
|
$this->router->get('/sites', ListSitesController::class, $adminCondition);
|
||||||
$this->router->get('/tcp', ListTcpConnectionsController::class, $adminCondition);
|
$this->router->get('/tcp', ListTcpConnectionsController::class, $adminCondition);
|
||||||
|
|
||||||
|
$this->router->get('/api/statistics', GetStatisticsController::class, $adminCondition);
|
||||||
$this->router->get('/api/settings', GetSettingsController::class, $adminCondition);
|
$this->router->get('/api/settings', GetSettingsController::class, $adminCondition);
|
||||||
$this->router->post('/api/settings', StoreSettingsController::class, $adminCondition);
|
$this->router->post('/api/settings', StoreSettingsController::class, $adminCondition);
|
||||||
$this->router->get('/api/users', GetUsersController::class, $adminCondition);
|
$this->router->get('/api/users', GetUsersController::class, $adminCondition);
|
||||||
@@ -179,6 +185,7 @@ class Factory
|
|||||||
->bindSubdomainRepository()
|
->bindSubdomainRepository()
|
||||||
->bindDatabase()
|
->bindDatabase()
|
||||||
->ensureDatabaseIsInitialized()
|
->ensureDatabaseIsInitialized()
|
||||||
|
->registerStatisticsCollector()
|
||||||
->bindConnectionManager()
|
->bindConnectionManager()
|
||||||
->addAdminRoutes();
|
->addAdminRoutes();
|
||||||
|
|
||||||
@@ -265,4 +272,26 @@ class Factory
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function registerStatisticsCollector()
|
||||||
|
{
|
||||||
|
if (config('expose.admin.statistics.enable_statistics', true) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app()->singleton(StatisticsRepository::class, function () {
|
||||||
|
return app(config('expose.admin.statistics.repository', DatabaseStatisticsRepository::class));
|
||||||
|
});
|
||||||
|
|
||||||
|
app()->singleton(StatisticsCollector::class, function () {
|
||||||
|
return app(DatabaseStatisticsCollector::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
$intervalInSeconds = config('expose.admin.statistics.interval_in_seconds', 3600);
|
||||||
|
|
||||||
|
$this->loop->addPeriodicTimer($intervalInSeconds, function () {
|
||||||
|
app(StatisticsCollector::class)->save();
|
||||||
|
});
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Contracts\ConnectionManager;
|
||||||
|
use App\Contracts\StatisticsRepository;
|
||||||
|
use App\Contracts\UserRepository;
|
||||||
|
use App\Server\Configuration;
|
||||||
|
use App\Server\Connections\ControlConnection;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class GetStatisticsController extends AdminController
|
||||||
|
{
|
||||||
|
protected $keepConnectionOpen = true;
|
||||||
|
|
||||||
|
/** @var StatisticsRepository */
|
||||||
|
protected $statisticsRepository;
|
||||||
|
|
||||||
|
public function __construct(StatisticsRepository $statisticsRepository)
|
||||||
|
{
|
||||||
|
$this->statisticsRepository = $statisticsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
|
{
|
||||||
|
$from = today()->subWeek()->toDateString();
|
||||||
|
$until = today()->toDateString();
|
||||||
|
|
||||||
|
$this->statisticsRepository->getStatistics($request->get('from', $from), $request->get('until', $until))
|
||||||
|
->then(function ($statistics) use ($httpConnection) {
|
||||||
|
$httpConnection->send(
|
||||||
|
respond_json([
|
||||||
|
'statistics' => $statistics,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$httpConnection->close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -235,7 +235,12 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
$deferred->reject();
|
$deferred->reject();
|
||||||
} else {
|
} else {
|
||||||
|
$this->userRepository
|
||||||
|
->updateLastSharedAt($user['id'])
|
||||||
|
->then(function () use ($deferred, $user) {
|
||||||
$deferred->resolve($user);
|
$deferred->resolve($user);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Server\Http\Controllers;
|
namespace App\Server\Http\Controllers;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
|
use App\Contracts\StatisticsCollector;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Server\Configuration;
|
use App\Server\Configuration;
|
||||||
use App\Server\Connections\ControlConnection;
|
use App\Server\Connections\ControlConnection;
|
||||||
@@ -27,10 +28,14 @@ class TunnelMessageController extends Controller
|
|||||||
|
|
||||||
protected $modifiers = [];
|
protected $modifiers = [];
|
||||||
|
|
||||||
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
/** @var StatisticsCollector */
|
||||||
|
protected $statisticsCollector;
|
||||||
|
|
||||||
|
public function __construct(ConnectionManager $connectionManager, StatisticsCollector $statisticsCollector, Configuration $configuration)
|
||||||
{
|
{
|
||||||
$this->connectionManager = $connectionManager;
|
$this->connectionManager = $connectionManager;
|
||||||
$this->configuration = $configuration;
|
$this->configuration = $configuration;
|
||||||
|
$this->statisticsCollector = $statisticsCollector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
@@ -57,6 +62,8 @@ class TunnelMessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->statisticsCollector->incomingRequest();
|
||||||
|
|
||||||
$this->sendRequestToClient($request, $controlConnection, $httpConnection);
|
$this->sendRequestToClient($request, $controlConnection, $httpConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
107
app/Server/StatisticsCollector/DatabaseStatisticsCollector.php
Normal file
107
app/Server/StatisticsCollector/DatabaseStatisticsCollector.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Server\StatisticsCollector;
|
||||||
|
|
||||||
|
use App\Contracts\ConnectionManager;
|
||||||
|
use App\Contracts\StatisticsCollector;
|
||||||
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
|
|
||||||
|
class DatabaseStatisticsCollector implements StatisticsCollector
|
||||||
|
{
|
||||||
|
/** @var DatabaseInterface */
|
||||||
|
protected $database;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $sharedPorts = [];
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $sharedSites = [];
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $requests = 0;
|
||||||
|
|
||||||
|
public function __construct(DatabaseInterface $database,)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush the stored statistics.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function flush()
|
||||||
|
{
|
||||||
|
$this->sharedPorts = [];
|
||||||
|
$this->sharedSites = [];
|
||||||
|
$this->requests = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function siteShared($authToken = null)
|
||||||
|
{
|
||||||
|
if (! $this->shouldCollectStatistics()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($this->sharedSites[$authToken])) {
|
||||||
|
$this->sharedSites[$authToken] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sharedSites[$authToken]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portShared($authToken = null)
|
||||||
|
{
|
||||||
|
if (! $this->shouldCollectStatistics()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($this->sharedPorts[$authToken])) {
|
||||||
|
$this->sharedPorts[$authToken] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sharedPorts[$authToken]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function incomingRequest()
|
||||||
|
{
|
||||||
|
if (! $this->shouldCollectStatistics()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->requests++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$sharedSites = 0;
|
||||||
|
collect($this->sharedSites)->map(function ($numSites) use (&$sharedSites) {
|
||||||
|
$sharedSites += $numSites;
|
||||||
|
});
|
||||||
|
|
||||||
|
$sharedPorts = 0;
|
||||||
|
collect($this->sharedPorts)->map(function ($numPorts) use (&$sharedPorts) {
|
||||||
|
$sharedPorts += $numPorts;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->database->query("
|
||||||
|
INSERT INTO statistics (timestamp, shared_sites, shared_ports, unique_shared_sites, unique_shared_ports, incoming_requests)
|
||||||
|
VALUES (:timestamp, :shared_sites, :shared_ports, :unique_shared_sites, :unique_shared_ports, :incoming_requests)
|
||||||
|
", [
|
||||||
|
'timestamp' => today()->toDateString(),
|
||||||
|
'shared_sites' => $sharedSites,
|
||||||
|
'shared_ports' => $sharedPorts,
|
||||||
|
'unique_shared_sites' => count($this->sharedSites),
|
||||||
|
'unique_shared_ports' => count($this->sharedPorts),
|
||||||
|
'incoming_requests' => $this->requests,
|
||||||
|
])
|
||||||
|
->then(function () {
|
||||||
|
$this->flush();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldCollectStatistics(): bool
|
||||||
|
{
|
||||||
|
return config('expose.admin.statistics.enable_statistics', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Server\StatisticsRepository;
|
||||||
|
|
||||||
|
use App\Contracts\StatisticsRepository;
|
||||||
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
|
use Clue\React\SQLite\Result;
|
||||||
|
use React\Promise\Deferred;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
class DatabaseStatisticsRepository implements StatisticsRepository
|
||||||
|
{
|
||||||
|
/** @var DatabaseInterface */
|
||||||
|
protected $database;
|
||||||
|
|
||||||
|
public function __construct(DatabaseInterface $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatistics($from, $until): PromiseInterface
|
||||||
|
{
|
||||||
|
$deferred = new Deferred();
|
||||||
|
|
||||||
|
$this->database
|
||||||
|
->query('SELECT
|
||||||
|
timestamp,
|
||||||
|
SUM(shared_sites) as shared_sites,
|
||||||
|
SUM(shared_ports) as shared_ports,
|
||||||
|
SUM(unique_shared_sites) as unique_shared_sites,
|
||||||
|
SUM(unique_shared_ports) as unique_shared_ports,
|
||||||
|
SUM(incoming_requests) as incoming_requests
|
||||||
|
FROM statistics
|
||||||
|
WHERE
|
||||||
|
`timestamp` >= "' . $from . '" AND `timestamp` <= "' . $until . '"')
|
||||||
|
->then(function (Result $result) use ($deferred) {
|
||||||
|
$deferred->resolve($result->rows);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $deferred->promise();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,6 +114,19 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateLastSharedAt($id): PromiseInterface
|
||||||
|
{
|
||||||
|
$deferred = new Deferred();
|
||||||
|
|
||||||
|
$this->database
|
||||||
|
->query("UPDATE users SET last_shared_at = date('now') WHERE id = :id", ["id" => $id])
|
||||||
|
->then(function (Result $result) use ($deferred) {
|
||||||
|
$deferred->resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $deferred->promise();
|
||||||
|
}
|
||||||
|
|
||||||
public function getUserByToken(string $authToken): PromiseInterface
|
public function getUserByToken(string $authToken): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|||||||
@@ -327,5 +327,13 @@ return [
|
|||||||
|
|
||||||
'tcp_port_sharing_disabled' => 'TCP port sharing is not available on this Expose server.',
|
'tcp_port_sharing_disabled' => 'TCP port sharing is not available on this Expose server.',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'statistics' => [
|
||||||
|
'enable_statistics' => true,
|
||||||
|
|
||||||
|
'interval_in_seconds' => 3600,
|
||||||
|
|
||||||
|
'repository' => \App\Server\StatisticsRepository\DatabaseStatisticsRepository::class,
|
||||||
|
]
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD last_shared_at DATETIME;
|
||||||
9
database/migrations/07_create_statistics_table.sql
Normal file
9
database/migrations/07_create_statistics_table.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS statistics (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp DATE,
|
||||||
|
shared_sites INTEGER,
|
||||||
|
shared_ports INTEGER,
|
||||||
|
unique_shared_sites INTEGER,
|
||||||
|
unique_shared_ports INTEGER,
|
||||||
|
incoming_requests INTEGER
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user