Transitioned from obsolete gateway object classes (`edge_gateway_shell_action_jobs_o`, `edge_gateway_shell_events_o`, `edge_gateway_shell_sessions_o`, `edge_gateway_update_jobs_o`) to the new agent implementation (`edge-gateway-agent/agent.php`).
292 lines
14 KiB
PHP
292 lines
14 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
/**
|
|
* Ensures additive department edge gateway tables exist.
|
|
*
|
|
* The legacy PHP stack has no centralized migration runner, so this bootstrap
|
|
* must be safe to call from runtime flows and tests.
|
|
*/
|
|
class edge_gateway_schema_bootstrap
|
|
{
|
|
private static bool $initialized = false;
|
|
|
|
public static function ensureTables(): void
|
|
{
|
|
if (self::$initialized) {
|
|
return;
|
|
}
|
|
|
|
global $db;
|
|
|
|
$queries = [
|
|
"CREATE TABLE IF NOT EXISTS edge_gateways (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
department_id INT NOT NULL,
|
|
label VARCHAR(255) NOT NULL,
|
|
hostname VARCHAR(255) NULL,
|
|
agent_token_hash VARCHAR(255) NOT NULL,
|
|
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
transport_mode VARCHAR(32) NOT NULL DEFAULT 'gateway',
|
|
release_channel VARCHAR(32) NOT NULL DEFAULT 'stable',
|
|
installed_version VARCHAR(64) NULL,
|
|
target_version VARCHAR(64) NULL,
|
|
last_heartbeat_at DATETIME NULL,
|
|
last_seen_ip VARCHAR(64) NULL,
|
|
discovery_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
is_primary TINYINT(1) NOT NULL DEFAULT 1,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL DEFAULT NULL,
|
|
INDEX idx_edge_gateways_department_status (department_id, status),
|
|
INDEX idx_edge_gateways_department_primary (department_id, is_primary),
|
|
INDEX idx_edge_gateways_last_heartbeat (last_heartbeat_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_claim_tokens (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
department_id INT NOT NULL,
|
|
label VARCHAR(255) NULL,
|
|
token_hash VARCHAR(255) NOT NULL,
|
|
created_by INT NULL,
|
|
expires_at DATETIME NOT NULL,
|
|
used_at DATETIME NULL,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL DEFAULT NULL,
|
|
INDEX idx_edge_gateway_claim_tokens_department (department_id),
|
|
INDEX idx_edge_gateway_claim_tokens_expires (expires_at),
|
|
INDEX idx_edge_gateway_claim_tokens_used (used_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_device_inventory (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
gateway_id INT NOT NULL,
|
|
device_id VARCHAR(255) NOT NULL,
|
|
local_ip VARCHAR(64) NULL,
|
|
model VARCHAR(255) NULL,
|
|
channel_count INT NOT NULL DEFAULT 1,
|
|
capabilities_json JSON NULL,
|
|
online TINYINT(1) NOT NULL DEFAULT 0,
|
|
last_seen_at DATETIME NULL,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL DEFAULT NULL,
|
|
UNIQUE KEY uniq_edge_gateway_inventory_device (gateway_id, device_id),
|
|
INDEX idx_edge_gateway_inventory_gateway (gateway_id),
|
|
INDEX idx_edge_gateway_inventory_ip (local_ip)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_relay_bindings (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
gateway_id INT NOT NULL,
|
|
department_id INT NOT NULL,
|
|
relay_id VARCHAR(255) NOT NULL,
|
|
device_id VARCHAR(255) NOT NULL,
|
|
local_ip VARCHAR(64) NULL,
|
|
channel INT NOT NULL DEFAULT 0,
|
|
fallback_mode VARCHAR(32) NOT NULL DEFAULT 'PREFER_LOCAL',
|
|
binding_source VARCHAR(64) NOT NULL DEFAULT 'MANUAL',
|
|
approved_by INT NULL,
|
|
approved_at DATETIME NULL,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL DEFAULT NULL,
|
|
UNIQUE KEY uniq_edge_gateway_binding (gateway_id, relay_id),
|
|
INDEX idx_edge_gateway_binding_department (department_id),
|
|
INDEX idx_edge_gateway_binding_device (gateway_id, device_id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_command_jobs (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
gateway_id INT NOT NULL,
|
|
command_type VARCHAR(64) NOT NULL,
|
|
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
request_json JSON NULL,
|
|
response_json JSON NULL,
|
|
delivery_json JSON NULL,
|
|
correlation_id VARCHAR(128) NOT NULL,
|
|
requested_by INT NULL,
|
|
requested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
completed_at DATETIME NULL,
|
|
error_message TEXT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL DEFAULT NULL,
|
|
UNIQUE KEY uniq_edge_gateway_command_correlation (correlation_id),
|
|
INDEX idx_edge_gateway_command_gateway (gateway_id),
|
|
INDEX idx_edge_gateway_command_status (status),
|
|
INDEX idx_edge_gateway_command_type (command_type)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_operations (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
gateway_id INT NOT NULL,
|
|
type VARCHAR(32) NOT NULL,
|
|
operation_type VARCHAR(32) NOT NULL DEFAULT 'DISCOVERY',
|
|
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
request_json JSON NULL,
|
|
summary_json JSON NULL,
|
|
result_json JSON NULL,
|
|
error_code VARCHAR(128) NULL,
|
|
error_message TEXT NULL,
|
|
correlation_id VARCHAR(128) NOT NULL,
|
|
agent_instance_id VARCHAR(128) NULL,
|
|
lease_expires_at DATETIME NULL,
|
|
last_progress_at DATETIME NULL,
|
|
attempt_count INT NOT NULL DEFAULT 0,
|
|
requested_by INT NULL,
|
|
requested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
started_at DATETIME NULL,
|
|
completed_at DATETIME NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL DEFAULT NULL,
|
|
UNIQUE KEY uniq_edge_gateway_operation_correlation (correlation_id),
|
|
INDEX idx_edge_gateway_operations_gateway (gateway_id),
|
|
INDEX idx_edge_gateway_operations_status (status),
|
|
INDEX idx_edge_gateway_operations_type (type)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_operation_events (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
operation_id INT NOT NULL,
|
|
gateway_id INT NOT NULL,
|
|
level VARCHAR(16) NOT NULL DEFAULT 'INFO',
|
|
code VARCHAR(128) NULL,
|
|
message TEXT NOT NULL,
|
|
context_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
INDEX idx_edge_gateway_operation_events_operation (operation_id),
|
|
INDEX idx_edge_gateway_operation_events_gateway (gateway_id),
|
|
INDEX idx_edge_gateway_operation_events_level (level)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
|
|
"CREATE TABLE IF NOT EXISTS edge_gateway_audit_logs (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
gateway_id INT NULL,
|
|
department_id INT NULL,
|
|
action VARCHAR(128) NOT NULL,
|
|
actor_user_id INT NULL,
|
|
actor_type VARCHAR(32) NOT NULL DEFAULT 'USER',
|
|
severity VARCHAR(16) NOT NULL DEFAULT 'INFO',
|
|
context_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
INDEX idx_edge_gateway_audit_gateway (gateway_id),
|
|
INDEX idx_edge_gateway_audit_department (department_id),
|
|
INDEX idx_edge_gateway_audit_action (action)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
|
];
|
|
|
|
foreach ($queries as $sql) {
|
|
$db->query($sql);
|
|
}
|
|
|
|
self::ensureColumn('edge_gateway_command_jobs', 'delivery_json', 'JSON NULL AFTER response_json');
|
|
|
|
self::ensureColumn('edge_gateway_relay_bindings', 'fallback_mode', "VARCHAR(32) NOT NULL DEFAULT 'PREFER_LOCAL' AFTER channel");
|
|
|
|
self::ensureColumn('edge_gateway_operations', 'type', "VARCHAR(32) NOT NULL DEFAULT 'DISCOVERY' AFTER gateway_id");
|
|
self::ensureColumn('edge_gateway_operations', 'operation_type', "VARCHAR(32) NOT NULL DEFAULT 'DISCOVERY' AFTER type");
|
|
self::ensureColumn('edge_gateway_operations', 'summary_json', 'JSON NULL AFTER request_json');
|
|
self::ensureColumn('edge_gateway_operations', 'result_json', 'JSON NULL AFTER summary_json');
|
|
self::ensureColumn('edge_gateway_operations', 'error_code', 'VARCHAR(128) NULL AFTER result_json');
|
|
self::ensureColumn('edge_gateway_operations', 'error_message', 'TEXT NULL AFTER error_code');
|
|
self::ensureColumn('edge_gateway_operations', 'correlation_id', 'VARCHAR(128) NULL AFTER error_message');
|
|
self::ensureColumn('edge_gateway_operations', 'agent_instance_id', 'VARCHAR(128) NULL AFTER correlation_id');
|
|
self::ensureColumn('edge_gateway_operations', 'lease_expires_at', 'DATETIME NULL AFTER agent_instance_id');
|
|
self::ensureColumn('edge_gateway_operations', 'last_progress_at', 'DATETIME NULL AFTER lease_expires_at');
|
|
self::ensureColumn('edge_gateway_operations', 'attempt_count', 'INT NOT NULL DEFAULT 0 AFTER last_progress_at');
|
|
self::ensureColumn('edge_gateway_operations', 'requested_by', 'INT NULL AFTER attempt_count');
|
|
self::ensureColumn('edge_gateway_operations', 'requested_at', 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER requested_by');
|
|
self::ensureColumn('edge_gateway_operations', 'started_at', 'DATETIME NULL AFTER requested_at');
|
|
self::ensureColumn('edge_gateway_operations', 'completed_at', 'DATETIME NULL AFTER started_at');
|
|
self::ensureColumn('edge_gateway_operations', 'updated_at', 'TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
|
self::ensureColumn('edge_gateway_operations', 'deleted_at', 'TIMESTAMP NULL DEFAULT NULL AFTER updated_at');
|
|
|
|
self::ensureColumn('edge_gateway_operation_events', 'code', 'VARCHAR(128) NULL AFTER level');
|
|
self::ensureColumn('edge_gateway_operation_events', 'context_json', 'JSON NULL AFTER message');
|
|
|
|
self::ensureColumn('edge_gateway_audit_logs', 'actor_type', "VARCHAR(32) NOT NULL DEFAULT 'USER' AFTER actor_user_id");
|
|
self::ensureColumn('edge_gateway_audit_logs', 'severity', "VARCHAR(16) NOT NULL DEFAULT 'INFO' AFTER actor_type");
|
|
self::ensureColumn('edge_gateway_audit_logs', 'context_json', 'JSON NULL AFTER severity');
|
|
|
|
self::syncOperationTypeColumns();
|
|
|
|
self::$initialized = true;
|
|
}
|
|
|
|
private static function ensureColumn(string $table, string $column, string $definition): void
|
|
{
|
|
global $db;
|
|
|
|
if (self::tableHasColumn($table, $column)) {
|
|
return;
|
|
}
|
|
|
|
if (!preg_match('/^[A-Za-z0-9_]+$/', $table) || !preg_match('/^[A-Za-z0-9_]+$/', $column)) {
|
|
throw new \RuntimeException('Invalid schema bootstrap identifier');
|
|
}
|
|
|
|
$db->query(
|
|
"ALTER TABLE `$table`
|
|
ADD COLUMN `$column` $definition"
|
|
);
|
|
}
|
|
|
|
private static function tableHasColumn(string $table, string $column): bool
|
|
{
|
|
global $db;
|
|
|
|
$table = $db->escape_string($table);
|
|
$column = $db->escape_string($column);
|
|
$database = $db->escape_string($db->getDatabase());
|
|
|
|
$result = $db->query(
|
|
"SELECT COUNT(*) AS c
|
|
FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = '$database'
|
|
AND TABLE_NAME = '$table'
|
|
AND COLUMN_NAME = '$column'"
|
|
);
|
|
|
|
if (!$result) {
|
|
return false;
|
|
}
|
|
|
|
$row = $result->fetch_assoc();
|
|
return ((int)($row['c'] ?? 0)) > 0;
|
|
}
|
|
|
|
private static function syncOperationTypeColumns(): void
|
|
{
|
|
global $db;
|
|
|
|
if (!self::tableHasColumn('edge_gateway_operations', 'type')
|
|
|| !self::tableHasColumn('edge_gateway_operations', 'operation_type')) {
|
|
return;
|
|
}
|
|
|
|
$db->query(
|
|
"UPDATE edge_gateway_operations
|
|
SET type = operation_type
|
|
WHERE operation_type IS NOT NULL
|
|
AND operation_type <> ''
|
|
AND (type IS NULL OR type = '' OR type <> operation_type)"
|
|
);
|
|
|
|
$db->query(
|
|
"UPDATE edge_gateway_operations
|
|
SET operation_type = type
|
|
WHERE type IS NOT NULL
|
|
AND type <> ''
|
|
AND (operation_type IS NULL OR operation_type = '')"
|
|
);
|
|
}
|
|
}
|