mirror of
https://github.com/getnamingo/registry.git
synced 2025-05-13 16:16:59 +02:00
Added user audit details in panel
This commit is contained in:
parent
2c48d48777
commit
6d1f934d36
8 changed files with 197 additions and 25 deletions
|
@ -204,6 +204,10 @@
|
||||||
"audit": true,
|
"audit": true,
|
||||||
"skip": null
|
"skip": null
|
||||||
},
|
},
|
||||||
|
"users_audit": {
|
||||||
|
"audit": true,
|
||||||
|
"skip": null
|
||||||
|
},
|
||||||
"users_confirmations": {
|
"users_confirmations": {
|
||||||
"audit": null,
|
"audit": null,
|
||||||
"skip": null
|
"skip": null
|
||||||
|
|
|
@ -277,6 +277,23 @@ class Auth
|
||||||
public static function changeCurrentPassword($oldPassword, $newPassword){
|
public static function changeCurrentPassword($oldPassword, $newPassword){
|
||||||
$auth = self::$auth;
|
$auth = self::$auth;
|
||||||
try {
|
try {
|
||||||
|
global $container;
|
||||||
|
$db = $container->get('db');
|
||||||
|
$currentDateTime = new \DateTime();
|
||||||
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
||||||
|
$db->insert(
|
||||||
|
'users_audit',
|
||||||
|
[
|
||||||
|
'user_id' => $_SESSION['auth_user_id'],
|
||||||
|
'user_event' => 'user.update.password',
|
||||||
|
'user_resource' => 'control.panel',
|
||||||
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
'user_ip' => get_client_ip(),
|
||||||
|
'user_location' => get_client_location(),
|
||||||
|
'event_time' => $currentDate,
|
||||||
|
'user_data' => null
|
||||||
|
]
|
||||||
|
);
|
||||||
$auth->changePassword($oldPassword, $newPassword);
|
$auth->changePassword($oldPassword, $newPassword);
|
||||||
redirect()->route('profile')->with('success','Password has been changed');
|
redirect()->route('profile')->with('success','Password has been changed');
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ class AuthController extends Controller
|
||||||
* @throws \Pinga\Auth\AuthError
|
* @throws \Pinga\Auth\AuthError
|
||||||
*/
|
*/
|
||||||
public function login(Request $request, Response $response){
|
public function login(Request $request, Response $response){
|
||||||
|
global $container;
|
||||||
|
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if(isset($data['remember'])){
|
if(isset($data['remember'])){
|
||||||
$remember = $data['remember'];
|
$remember = $data['remember'];
|
||||||
|
@ -71,8 +73,25 @@ class AuthController extends Controller
|
||||||
$code = null;
|
$code = null;
|
||||||
}
|
}
|
||||||
$login = Auth::login($data['email'], $data['password'], $remember, $code);
|
$login = Auth::login($data['email'], $data['password'], $remember, $code);
|
||||||
if($login===true)
|
if($login===true) {
|
||||||
|
$db = $container->get('db');
|
||||||
|
$currentDateTime = new \DateTime();
|
||||||
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
||||||
|
$db->insert(
|
||||||
|
'users_audit',
|
||||||
|
[
|
||||||
|
'user_id' => $_SESSION['auth_user_id'],
|
||||||
|
'user_event' => 'user.login',
|
||||||
|
'user_resource' => 'control.panel',
|
||||||
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
'user_ip' => get_client_ip(),
|
||||||
|
'user_location' => get_client_location(),
|
||||||
|
'event_time' => $currentDate,
|
||||||
|
'user_data' => null
|
||||||
|
]
|
||||||
|
);
|
||||||
redirect()->route('home');
|
redirect()->route('home');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,6 +99,23 @@ class AuthController extends Controller
|
||||||
*/
|
*/
|
||||||
public function logout()
|
public function logout()
|
||||||
{
|
{
|
||||||
|
global $container;
|
||||||
|
$db = $container->get('db');
|
||||||
|
$currentDateTime = new \DateTime();
|
||||||
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
||||||
|
$db->insert(
|
||||||
|
'users_audit',
|
||||||
|
[
|
||||||
|
'user_id' => $_SESSION['auth_user_id'],
|
||||||
|
'user_event' => 'user.logout',
|
||||||
|
'user_resource' => 'control.panel',
|
||||||
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
'user_ip' => get_client_ip(),
|
||||||
|
'user_location' => get_client_location(),
|
||||||
|
'event_time' => $currentDate,
|
||||||
|
'user_data' => null
|
||||||
|
]
|
||||||
|
);
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
redirect()->route('login');
|
redirect()->route('login');
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,12 +73,16 @@ class ProfileController extends Controller
|
||||||
'SELECT * FROM users_webauthn WHERE user_id = ?',
|
'SELECT * FROM users_webauthn WHERE user_id = ?',
|
||||||
[$userId]
|
[$userId]
|
||||||
);
|
);
|
||||||
|
$user_audit = $db->select(
|
||||||
|
'SELECT * FROM users_audit WHERE user_id = ? ORDER BY event_time DESC',
|
||||||
|
[$userId]
|
||||||
|
);
|
||||||
if ($is_2fa_activated) {
|
if ($is_2fa_activated) {
|
||||||
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue]);
|
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'userAudit' => $user_audit]);
|
||||||
} else if ($is_weba_activated) {
|
} else if ($is_weba_activated) {
|
||||||
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'weba' => $is_weba_activated]);
|
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'weba' => $is_weba_activated, 'userAudit' => $user_audit]);
|
||||||
} else {
|
} else {
|
||||||
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue]);
|
return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue, 'userAudit' => $user_audit]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,6 +118,21 @@ class ProfileController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$currentDateTime = new \DateTime();
|
||||||
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
||||||
|
$db->insert(
|
||||||
|
'users_audit',
|
||||||
|
[
|
||||||
|
'user_id' => $_SESSION['auth_user_id'],
|
||||||
|
'user_event' => 'user.enable.2fa',
|
||||||
|
'user_resource' => 'control.panel',
|
||||||
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
'user_ip' => get_client_ip(),
|
||||||
|
'user_location' => get_client_location(),
|
||||||
|
'event_time' => $currentDate,
|
||||||
|
'user_data' => null
|
||||||
|
]
|
||||||
|
);
|
||||||
$db->update(
|
$db->update(
|
||||||
'users',
|
'users',
|
||||||
[
|
[
|
||||||
|
|
|
@ -396,4 +396,33 @@ function createUuidFromId($id) {
|
||||||
// Handle exception
|
// Handle exception
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get the client IP address
|
||||||
|
function get_client_ip() {
|
||||||
|
$ipaddress = '';
|
||||||
|
if (getenv('HTTP_CLIENT_IP'))
|
||||||
|
$ipaddress = getenv('HTTP_CLIENT_IP');
|
||||||
|
else if(getenv('HTTP_X_FORWARDED_FOR'))
|
||||||
|
$ipaddress = getenv('HTTP_X_FORWARDED_FOR');
|
||||||
|
else if(getenv('HTTP_X_FORWARDED'))
|
||||||
|
$ipaddress = getenv('HTTP_X_FORWARDED');
|
||||||
|
else if(getenv('HTTP_FORWARDED_FOR'))
|
||||||
|
$ipaddress = getenv('HTTP_FORWARDED_FOR');
|
||||||
|
else if(getenv('HTTP_FORWARDED'))
|
||||||
|
$ipaddress = getenv('HTTP_FORWARDED');
|
||||||
|
else if(getenv('REMOTE_ADDR'))
|
||||||
|
$ipaddress = getenv('REMOTE_ADDR');
|
||||||
|
else
|
||||||
|
$ipaddress = 'UNKNOWN';
|
||||||
|
return $ipaddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_client_location() {
|
||||||
|
$PublicIP = get_client_ip();
|
||||||
|
$json = file_get_contents("http://ipinfo.io/$PublicIP/geo");
|
||||||
|
$json = json_decode($json, true);
|
||||||
|
$country = $json['country'];
|
||||||
|
|
||||||
|
return $country;
|
||||||
}
|
}
|
|
@ -36,6 +36,9 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#tabs-webauthn" class="nav-link" data-bs-toggle="tab">WebAuthn</a>
|
<a href="#tabs-webauthn" class="nav-link" data-bs-toggle="tab">WebAuthn</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#tabs-log" class="nav-link" data-bs-toggle="tab">Log</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -158,19 +161,55 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for device in weba %}
|
{% for device in weba %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ device.user_agent }}</td>
|
<td>{{ device.user_agent }}</td>
|
||||||
<td>{{ device.created_at }}</td>
|
<td>{{ device.created_at }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/path/to/action?deviceId={{ device.id }}">Edit</a>
|
<a href="/path/to/action?deviceId={{ device.id }}">Edit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">No devices found.</td>
|
<td colspan="3">No devices found.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="tabs-log">
|
||||||
|
<h4 class="card-title">User Audit Log</h4>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Track and review all user activities in your account below. Monitor logins, profile changes, and other key actions to ensure security and transparency.</p>
|
||||||
|
<div class="table-responsive mt-4">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Event</th>
|
||||||
|
<th>User Agent</th>
|
||||||
|
<th>IP</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in userAudit %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.user_event }}</td>
|
||||||
|
<td>{{ user.user_agent }}</td>
|
||||||
|
<td>{{ user.user_ip }}</td>
|
||||||
|
<td>{{ user.user_location }}</td>
|
||||||
|
<td>{{ user.event_time }}</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">No log data for user.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -601,6 +601,20 @@ CREATE TABLE IF NOT EXISTS `registry`.`users` (
|
||||||
UNIQUE KEY `email` (`email`)
|
UNIQUE KEY `email` (`email`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `registry`.`users_audit` (
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`user_event` VARCHAR(255) NOT NULL,
|
||||||
|
`user_resource` VARCHAR(255) default NULL,
|
||||||
|
`user_agent` VARCHAR(255) NOT NULL,
|
||||||
|
`user_ip` VARCHAR(45) NOT NULL,
|
||||||
|
`user_location` VARCHAR(45) default NULL,
|
||||||
|
`event_time` DATETIME(3) NOT NULL,
|
||||||
|
`user_data` JSON default NULL,
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `user_event` (`user_event`),
|
||||||
|
KEY `user_ip` (`user_ip`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel User Audit';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `registry`.`users_confirmations` (
|
CREATE TABLE IF NOT EXISTS `registry`.`users_confirmations` (
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
@ -616,24 +630,24 @@ CREATE TABLE IF NOT EXISTS `registry`.`users_confirmations` (
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `registry`.`users_remembered` (
|
CREATE TABLE IF NOT EXISTS `registry`.`users_remembered` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user` int(10) unsigned NOT NULL,
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
`selector` varchar(24) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
`selector` varchar(24) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
||||||
`token` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
`token` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
||||||
`expires` int(10) unsigned NOT NULL,
|
`expires` int(10) unsigned NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `selector` (`selector`),
|
UNIQUE KEY `selector` (`selector`),
|
||||||
KEY `user` (`user`)
|
KEY `user_id` (`user_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users Remember';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users Remember';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `registry`.`users_resets` (
|
CREATE TABLE IF NOT EXISTS `registry`.`users_resets` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user` int(10) unsigned NOT NULL,
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
`selector` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
`selector` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
||||||
`token` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
`token` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
|
||||||
`expires` int(10) unsigned NOT NULL,
|
`expires` int(10) unsigned NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `selector` (`selector`),
|
UNIQUE KEY `selector` (`selector`),
|
||||||
KEY `user_expires` (`user`,`expires`)
|
KEY `user_expires` (`user_id`,`expires`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users Reset';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Panel Users Reset';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `registry`.`users_throttling` (
|
CREATE TABLE IF NOT EXISTS `registry`.`users_throttling` (
|
||||||
|
|
|
@ -573,6 +573,20 @@ CREATE TABLE IF NOT EXISTS registry.users (
|
||||||
"backup_codes" TEXT,
|
"backup_codes" TEXT,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS registry.users_audit (
|
||||||
|
"user_id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
|
"user_event" VARCHAR(255) NOT NULL,
|
||||||
|
"user_resource" VARCHAR(255) DEFAULT NULL,
|
||||||
|
"user_agent" VARCHAR(255) NOT NULL,
|
||||||
|
"user_ip" VARCHAR(45) NOT NULL,
|
||||||
|
"user_location" VARCHAR(45) DEFAULT NULL,
|
||||||
|
"event_time" TIMESTAMP(3) NOT NULL,
|
||||||
|
"user_data" JSONB DEFAULT NULL,
|
||||||
|
CONSTRAINT pk_users_audit PRIMARY KEY (user_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_user_event ON registry.users_audit (user_event);
|
||||||
|
CREATE INDEX idx_user_ip ON registry.users_audit (user_ip);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS registry.users_confirmations (
|
CREATE TABLE IF NOT EXISTS registry.users_confirmations (
|
||||||
"id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
|
"id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
|
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
|
||||||
|
@ -586,21 +600,21 @@ CREATE INDEX IF NOT EXISTS "user_id" ON registry.users_confirmations ("user_id")
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS registry.users_remembered (
|
CREATE TABLE IF NOT EXISTS registry.users_remembered (
|
||||||
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
|
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
"user" INTEGER NOT NULL CHECK ("user" >= 0),
|
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
|
||||||
"selector" VARCHAR(24) UNIQUE NOT NULL,
|
"selector" VARCHAR(24) UNIQUE NOT NULL,
|
||||||
"token" VARCHAR(255) NOT NULL,
|
"token" VARCHAR(255) NOT NULL,
|
||||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS "user" ON registry.users_remembered ("user");
|
CREATE INDEX IF NOT EXISTS "user_id" ON registry.users_remembered ("user_id");
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS registry.users_resets (
|
CREATE TABLE IF NOT EXISTS registry.users_resets (
|
||||||
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
|
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
"user" INTEGER NOT NULL CHECK ("user" >= 0),
|
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
|
||||||
"selector" VARCHAR(20) UNIQUE NOT NULL,
|
"selector" VARCHAR(20) UNIQUE NOT NULL,
|
||||||
"token" VARCHAR(255) NOT NULL,
|
"token" VARCHAR(255) NOT NULL,
|
||||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS "user_expires" ON registry.users_resets ("user", "expires");
|
CREATE INDEX IF NOT EXISTS "user_expires" ON registry.users_resets ("user_id", "expires");
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS registry.users_throttling (
|
CREATE TABLE IF NOT EXISTS registry.users_throttling (
|
||||||
"bucket" VARCHAR(44) PRIMARY KEY,
|
"bucket" VARCHAR(44) PRIMARY KEY,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue