Add broker diagnostics and enhance relay logging
- Implement `diagnoseBrokerConfiguration` to validate broker URLs, shared secrets, and connection health. - Add diagnostic methods for shared secret validation, including legacy sync support. - Extend relay logging with descriptive context (`relay_name`, `relay_role`) and dynamic messaging. - Update tests to cover broker health and shared secret diagnostics.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace classes;
|
||||
|
||||
use Exception;
|
||||
use objects\department_relays_o;
|
||||
use objects\departments_o;
|
||||
use objects\department_variables_o;
|
||||
use objects\edge_gateway_audit_logs_o;
|
||||
@@ -4025,6 +4026,342 @@ BASH;
|
||||
return $secret !== null && hash_equals($configured, trim($secret));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function diagnoseBrokerConfiguration(array $options = []): array
|
||||
{
|
||||
$target = strtolower(trim((string)($options['target'] ?? 'all')));
|
||||
$target = in_array($target, ['internal', 'public', 'secret', 'all'], true) ? $target : 'all';
|
||||
|
||||
$internalUrl = $this->normalizeBrokerDiagnosticBaseUrl(
|
||||
array_key_exists('broker_url', $options) ? $options['broker_url'] : $this->configuredBrokerInternalUrl()
|
||||
);
|
||||
$publicConfigured = array_key_exists('public_broker_url', $options)
|
||||
? trim((string)$options['public_broker_url'])
|
||||
: $this->configuredPublicBrokerUrl();
|
||||
$publicUrl = $this->normalizeBrokerDiagnosticBaseUrl(
|
||||
$publicConfigured !== '' ? $publicConfigured : $this->deriveBrokerPublicUrl()
|
||||
);
|
||||
$sharedSecret = array_key_exists('broker_shared_secret', $options)
|
||||
? trim((string)$options['broker_shared_secret'])
|
||||
: $this->configuredBrokerSharedSecret();
|
||||
|
||||
$diagnostics = [
|
||||
'target' => $target,
|
||||
'checked_at' => $this->now(),
|
||||
'broker_auth_mode' => array_key_exists('broker_auth_mode', $options)
|
||||
? trim((string)$options['broker_auth_mode'])
|
||||
: $this->configuredBrokerAuthMode(),
|
||||
'broker_shared_secret_configured' => $sharedSecret !== '',
|
||||
];
|
||||
|
||||
if ($target === 'internal' || $target === 'all') {
|
||||
$diagnostics['internal_broker_connection'] = $this->diagnoseBrokerHttpEndpoint(
|
||||
$internalUrl,
|
||||
'Internal broker'
|
||||
);
|
||||
}
|
||||
|
||||
if ($target === 'public' || $target === 'all') {
|
||||
$diagnostics['public_broker_url'] = $this->diagnoseBrokerHttpEndpoint(
|
||||
$publicUrl,
|
||||
'Public broker'
|
||||
);
|
||||
}
|
||||
|
||||
if ($target === 'secret' || $target === 'all') {
|
||||
$diagnostics['broker_shared_secret'] = $this->diagnoseBrokerSharedSecret(
|
||||
$internalUrl,
|
||||
$sharedSecret
|
||||
);
|
||||
}
|
||||
|
||||
return $diagnostics;
|
||||
}
|
||||
|
||||
private function deriveBrokerPublicUrl(): ?string
|
||||
{
|
||||
$apiBaseUrl = $this->getApiBaseUrl();
|
||||
if (trim($apiBaseUrl) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rtrim($apiBaseUrl, '/') . '/edge-broker';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{url:?string,error:?string}
|
||||
*/
|
||||
private function normalizeBrokerDiagnosticBaseUrl(mixed $value): array
|
||||
{
|
||||
$url = trim((string)$value);
|
||||
if ($url === '') {
|
||||
return [
|
||||
'url' => null,
|
||||
'error' => 'not_configured',
|
||||
];
|
||||
}
|
||||
|
||||
$parsed = parse_url($url);
|
||||
$scheme = is_array($parsed) ? strtolower((string)($parsed['scheme'] ?? '')) : '';
|
||||
$host = is_array($parsed) ? trim((string)($parsed['host'] ?? '')) : '';
|
||||
if (!is_array($parsed) || $host === '' || !in_array($scheme, ['http', 'https'], true)) {
|
||||
return [
|
||||
'url' => $url,
|
||||
'error' => 'invalid_url',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => rtrim($url, '/'),
|
||||
'error' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{url:?string,error:?string} $baseUrl
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function diagnoseBrokerHttpEndpoint(array $baseUrl, string $label): array
|
||||
{
|
||||
if ($baseUrl['url'] === null || $baseUrl['error'] !== null) {
|
||||
return $this->brokerDiagnosticUrlFailure($baseUrl, $label);
|
||||
}
|
||||
|
||||
$health = $this->brokerHttpProbe($baseUrl['url'] . '/api/health');
|
||||
if (($health['status_code'] ?? null) === 200 && !empty($health['json']['ok'])) {
|
||||
return array_merge($health, [
|
||||
'ok' => true,
|
||||
'status' => 'connected',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => $label . ' responded to the health check.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (($health['status_code'] ?? null) === 404 && $this->isBrokerNotFoundProbe($health)) {
|
||||
return array_merge($health, [
|
||||
'ok' => true,
|
||||
'status' => 'connected_legacy',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => $label . ' responded, but the health endpoint is not deployed yet.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (($health['status_code'] ?? null) !== null) {
|
||||
return array_merge($health, [
|
||||
'ok' => false,
|
||||
'status' => 'unexpected_response',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => $label . ' returned HTTP ' . (string)$health['status_code'] . ' instead of the broker health response.',
|
||||
]);
|
||||
}
|
||||
|
||||
return array_merge($health, [
|
||||
'ok' => false,
|
||||
'status' => 'unreachable',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => $label . ' did not respond.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{url:?string,error:?string} $baseUrl
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function diagnoseBrokerSharedSecret(array $baseUrl, string $sharedSecret): array
|
||||
{
|
||||
if ($baseUrl['url'] === null || $baseUrl['error'] !== null) {
|
||||
return $this->brokerDiagnosticUrlFailure($baseUrl, 'Internal broker');
|
||||
}
|
||||
|
||||
$headers = $sharedSecret !== '' ? ['x-edge-broker-secret: ' . $sharedSecret] : [];
|
||||
$diagnostic = $this->brokerHttpProbe(
|
||||
$baseUrl['url'] . '/api/diagnostics/shared-secret',
|
||||
'POST',
|
||||
[],
|
||||
$headers
|
||||
);
|
||||
|
||||
if (($diagnostic['status_code'] ?? null) === 200 && !empty($diagnostic['json']['ok'])) {
|
||||
$required = (bool)($diagnostic['json']['shared_secret_required'] ?? false);
|
||||
return array_merge($diagnostic, [
|
||||
'ok' => true,
|
||||
'status' => $required ? 'validated' : 'not_required',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => $required
|
||||
? 'Broker accepted the configured shared secret.'
|
||||
: 'Broker responded and does not currently require a shared secret.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (($diagnostic['status_code'] ?? null) === 403) {
|
||||
return array_merge($diagnostic, [
|
||||
'ok' => false,
|
||||
'status' => 'secret_rejected',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => 'Broker rejected the configured shared secret.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (($diagnostic['status_code'] ?? null) === 404 && $this->isBrokerNotFoundProbe($diagnostic)) {
|
||||
return $this->diagnoseBrokerSharedSecretWithLegacySync($baseUrl, $headers);
|
||||
}
|
||||
|
||||
if (($diagnostic['status_code'] ?? null) !== null) {
|
||||
return array_merge($diagnostic, [
|
||||
'ok' => false,
|
||||
'status' => 'unexpected_response',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => 'Broker returned HTTP ' . (string)$diagnostic['status_code'] . ' during shared secret validation.',
|
||||
]);
|
||||
}
|
||||
|
||||
return array_merge($diagnostic, [
|
||||
'ok' => false,
|
||||
'status' => 'unreachable',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => 'Internal broker did not respond during shared secret validation.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{url:?string,error:?string} $baseUrl
|
||||
* @param array<int,string> $headers
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function diagnoseBrokerSharedSecretWithLegacySync(array $baseUrl, array $headers): array
|
||||
{
|
||||
$legacy = $this->brokerHttpProbe(
|
||||
$baseUrl['url'] . '/api/gateways/0/sync',
|
||||
'POST',
|
||||
['diagnostic' => true],
|
||||
$headers
|
||||
);
|
||||
|
||||
if (($legacy['status_code'] ?? null) === 200 && !empty($legacy['json']['ok'])) {
|
||||
return array_merge($legacy, [
|
||||
'ok' => true,
|
||||
'status' => 'validated_legacy',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => 'Broker accepted the shared secret through the legacy sync endpoint.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (($legacy['status_code'] ?? null) === 403) {
|
||||
return array_merge($legacy, [
|
||||
'ok' => false,
|
||||
'status' => 'secret_rejected',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => 'Broker rejected the configured shared secret.',
|
||||
]);
|
||||
}
|
||||
|
||||
return array_merge($legacy, [
|
||||
'ok' => false,
|
||||
'status' => ($legacy['status_code'] ?? null) === null ? 'unreachable' : 'unexpected_response',
|
||||
'url' => $baseUrl['url'],
|
||||
'message' => 'Broker shared secret could not be validated.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{url:?string,error:?string} $baseUrl
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function brokerDiagnosticUrlFailure(array $baseUrl, string $label): array
|
||||
{
|
||||
$error = (string)($baseUrl['error'] ?? 'not_configured');
|
||||
return [
|
||||
'ok' => false,
|
||||
'status' => $error,
|
||||
'url' => $baseUrl['url'],
|
||||
'status_code' => null,
|
||||
'elapsed_ms' => 0,
|
||||
'message' => $error === 'invalid_url'
|
||||
? $label . ' URL is not a valid http(s) URL.'
|
||||
: $label . ' URL is not configured.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $response
|
||||
*/
|
||||
private function isBrokerNotFoundProbe(array $response): bool
|
||||
{
|
||||
$json = isset($response['json']) && is_array($response['json']) ? (array)$response['json'] : [];
|
||||
return strtolower(trim((string)($json['error'] ?? ''))) === 'not found';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,string> $headers
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function brokerHttpProbe(
|
||||
string $url,
|
||||
string $method = 'GET',
|
||||
?array $payload = null,
|
||||
array $headers = [],
|
||||
int $timeoutSeconds = 3
|
||||
): array {
|
||||
$method = strtoupper(trim($method)) ?: 'GET';
|
||||
$requestHeaders = array_filter(array_merge(['Accept: application/json'], $headers));
|
||||
$options = [
|
||||
'method' => $method,
|
||||
'header' => implode("\r\n", $requestHeaders),
|
||||
'timeout' => max(1, $timeoutSeconds),
|
||||
'ignore_errors' => true,
|
||||
];
|
||||
|
||||
if ($payload !== null) {
|
||||
$requestHeaders[] = 'Content-Type: application/json';
|
||||
$options['header'] = implode("\r\n", array_filter($requestHeaders));
|
||||
$options['content'] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
$context = stream_context_create(['http' => $options]);
|
||||
$started = microtime(true);
|
||||
$body = @file_get_contents($url, false, $context);
|
||||
$elapsedMs = (int)round((microtime(true) - $started) * 1000);
|
||||
$responseHeaders = is_array($http_response_header ?? null) ? $http_response_header : [];
|
||||
$statusCode = $this->parseHttpStatusCode($responseHeaders);
|
||||
|
||||
if ($body === false) {
|
||||
$lastError = error_get_last();
|
||||
return [
|
||||
'status_code' => $statusCode,
|
||||
'elapsed_ms' => $elapsedMs,
|
||||
'error' => isset($lastError['message']) ? self::trimInstallSessionText($lastError['message'], 512) : null,
|
||||
'json' => null,
|
||||
'body_excerpt' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$decoded = json_decode($body, true);
|
||||
return [
|
||||
'status_code' => $statusCode,
|
||||
'elapsed_ms' => $elapsedMs,
|
||||
'error' => null,
|
||||
'json' => is_array($decoded) ? $decoded : null,
|
||||
'body_excerpt' => self::trimInstallSessionText($body, 512),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,string> $headers
|
||||
*/
|
||||
private function parseHttpStatusCode(array $headers): ?int
|
||||
{
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match('/^HTTP\/\S+\s+(\d{3})\b/i', trim((string)$header), $matches) === 1) {
|
||||
return (int)$matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -4406,6 +4743,14 @@ BASH;
|
||||
if ($relayId !== '' && !isset($signal['relay_id'])) {
|
||||
$signal['relay_id'] = $relayId;
|
||||
}
|
||||
$relayRole = $this->normalizeRelayLogRole(
|
||||
$actionContext['relay_role']
|
||||
?? $actionContext['role']
|
||||
?? $binding['metadata']['relay_role']
|
||||
?? $binding['metadata']['role']
|
||||
?? null
|
||||
);
|
||||
$relayName = $this->resolveRelayLogDisplayName($binding, $actionContext, $signal);
|
||||
|
||||
$context = [
|
||||
'success' => $success,
|
||||
@@ -4418,6 +4763,8 @@ BASH;
|
||||
'department_id' => isset($binding['department_id']) ? (int)$binding['department_id'] : null,
|
||||
'gateway_id' => isset($binding['gateway_id']) ? (int)$binding['gateway_id'] : null,
|
||||
'relay_id' => $relayId !== '' ? $relayId : null,
|
||||
'relay_name' => $relayName,
|
||||
'relay_role' => $relayRole,
|
||||
'action' => strtoupper(trim((string)($dispatchLog['action'] ?? 'RELAY'))),
|
||||
'target_on' => array_key_exists('target_on', $dispatchLog) ? $dispatchLog['target_on'] : null,
|
||||
'toggle_after_seconds' => $dispatchLog['toggle_after_seconds'] ?? null,
|
||||
@@ -4453,42 +4800,211 @@ BASH;
|
||||
$extraContext['admin_user_id'],
|
||||
$extraContext['customer_user_id'],
|
||||
$extraContext['customer_number'],
|
||||
$extraContext['subuser_id']
|
||||
$extraContext['subuser_id'],
|
||||
$extraContext['relay_name'],
|
||||
$extraContext['relay_label'],
|
||||
$extraContext['relay_role'],
|
||||
$extraContext['role']
|
||||
);
|
||||
if ($extraContext !== []) {
|
||||
$context['action_context'] = $this->sanitizeRelayLogValue($extraContext);
|
||||
}
|
||||
|
||||
$context = $this->compactRelayLogArray($context);
|
||||
$description = $this->buildRelayLogDescription($context, $success);
|
||||
if ($description !== '') {
|
||||
$context['description'] = $description;
|
||||
}
|
||||
|
||||
return $this->compactRelayLogArray($context);
|
||||
}
|
||||
|
||||
private function buildRelayLogMessage(array $context, bool $success): string
|
||||
{
|
||||
$action = strtoupper(trim((string)($context['action'] ?? 'RELAY')));
|
||||
$relayId = trim((string)($context['relay_id'] ?? 'unknown'));
|
||||
$description = trim((string)($context['description'] ?? ''));
|
||||
if ($description !== '') {
|
||||
return $description;
|
||||
}
|
||||
|
||||
return $this->buildRelayLogDescription($context, $success);
|
||||
}
|
||||
|
||||
private function buildRelayLogDescription(array $context, bool $success): string
|
||||
{
|
||||
$subject = $this->buildRelayLogSubject($context);
|
||||
$handler = strtolower(trim((string)($context['handler'] ?? 'local')));
|
||||
$channel = trim((string)($context['delivery_channel'] ?? ''));
|
||||
|
||||
if (!$success) {
|
||||
return sprintf('Relay %s %s failed via %s', $action, $relayId, $handler);
|
||||
return trim(sprintf('%s failed via %s', $subject, $handler));
|
||||
}
|
||||
|
||||
return trim(sprintf(
|
||||
'Relay %s %s handled by %s%s',
|
||||
$action,
|
||||
$relayId,
|
||||
'%s handled by %s%s',
|
||||
$subject,
|
||||
$handler,
|
||||
$channel !== '' ? ' via ' . $channel : ''
|
||||
));
|
||||
}
|
||||
|
||||
private function buildRelayLogSubject(array $context): string
|
||||
{
|
||||
$action = strtoupper(trim((string)($context['action'] ?? 'RELAY')));
|
||||
$relayId = trim((string)($context['relay_id'] ?? 'unknown'));
|
||||
$relayName = trim((string)($context['relay_name'] ?? ''));
|
||||
$relayTarget = $relayName !== '' ? $relayName : $relayId;
|
||||
$relayRole = $this->normalizeRelayLogRole($context['relay_role'] ?? null);
|
||||
$state = $this->resolveRelayLogState($context);
|
||||
|
||||
if (in_array($relayRole, ['ENTRY', 'EXIT'], true)) {
|
||||
$verb = $state === false ? 'Close' : 'Open';
|
||||
return trim(sprintf('%s %s %s', $verb, $relayRole, $relayTarget));
|
||||
}
|
||||
|
||||
if (in_array($relayRole, ['MACHINE', 'PROGRAM_PICKER', 'CLEANER'], true) && $state !== null) {
|
||||
return trim(sprintf('%s %s %s', $relayRole, $state ? 'ON' : 'OFF', $relayTarget));
|
||||
}
|
||||
|
||||
$targetState = $state !== null ? ($state ? ' ON' : ' OFF') : '';
|
||||
return trim(sprintf('Relay %s %s%s', $action, $relayTarget, $targetState));
|
||||
}
|
||||
|
||||
private function resolveRelayLogState(array $context): ?bool
|
||||
{
|
||||
if (array_key_exists('target_on', $context) && $context['target_on'] !== null) {
|
||||
return (bool)$context['target_on'];
|
||||
}
|
||||
|
||||
$signal = isset($context['signal']) && is_array($context['signal']) ? (array)$context['signal'] : [];
|
||||
$request = isset($signal['request']) && is_array($signal['request']) ? (array)$signal['request'] : [];
|
||||
if (array_key_exists('on', $request)) {
|
||||
return (bool)$request['on'];
|
||||
}
|
||||
|
||||
$response = isset($context['response']) && is_array($context['response']) ? (array)$context['response'] : [];
|
||||
if (array_key_exists('on', $response)) {
|
||||
return (bool)$response['on'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizeRelayLogRole(mixed $role): ?string
|
||||
{
|
||||
$normalized = strtoupper(trim((string)($role ?? '')));
|
||||
if ($normalized === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($normalized) {
|
||||
'ENTRANCE', 'IN', 'INLET', 'ENTRY_GATE' => 'ENTRY',
|
||||
'OUT', 'OUTLET', 'EXIT_GATE' => 'EXIT',
|
||||
'MACHINE_PROGRAM_PICKER', 'PROGRAM_SELECTOR', 'PICKER' => 'PROGRAM_PICKER',
|
||||
'MACHINE_CLEANER' => 'CLEANER',
|
||||
default => $normalized,
|
||||
};
|
||||
}
|
||||
|
||||
private function resolveRelayLogDisplayName(array $binding, array $actionContext, array $signal): ?string
|
||||
{
|
||||
$metadata = isset($binding['metadata']) && is_array($binding['metadata']) ? (array)$binding['metadata'] : [];
|
||||
$request = isset($signal['request']) && is_array($signal['request']) ? (array)$signal['request'] : [];
|
||||
$relayId = trim((string)($signal['relay_id'] ?? $binding['relay_id'] ?? $request['relayId'] ?? $request['id'] ?? ''));
|
||||
|
||||
$directName = $this->firstRelayLogString([
|
||||
$actionContext['relay_name'] ?? null,
|
||||
$actionContext['relay_label'] ?? null,
|
||||
$metadata['relay_name'] ?? null,
|
||||
$metadata['relay_label'] ?? null,
|
||||
$metadata['name'] ?? null,
|
||||
$metadata['label'] ?? null,
|
||||
$request['relay_name'] ?? null,
|
||||
$request['relay_label'] ?? null,
|
||||
]);
|
||||
if ($directName !== null) {
|
||||
return $directName;
|
||||
}
|
||||
|
||||
$departmentName = $this->findDepartmentRelayName(
|
||||
isset($binding['department_id']) ? (int)$binding['department_id'] : 0,
|
||||
$relayId
|
||||
);
|
||||
if ($departmentName !== null) {
|
||||
return $departmentName;
|
||||
}
|
||||
|
||||
foreach ($this->listShellyRelayOptionsForLocalIpLookup() as $option) {
|
||||
$optionRelayId = trim((string)($option['id'] ?? ''));
|
||||
if ($optionRelayId === '' || $optionRelayId !== $relayId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$optionName = $this->firstRelayLogString([
|
||||
$option['name'] ?? null,
|
||||
$option['label'] ?? null,
|
||||
$option['device_name'] ?? null,
|
||||
]);
|
||||
if ($optionName !== null) {
|
||||
return $optionName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function firstRelayLogString(array $candidates): ?string
|
||||
{
|
||||
foreach ($candidates as $candidate) {
|
||||
$value = trim((string)($candidate ?? ''));
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findDepartmentRelayName(int $departmentId, string $relayId): ?string
|
||||
{
|
||||
if ($departmentId <= 0 || trim($relayId) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = (new department_relays_o())->getFieldsWhere([
|
||||
'department' => $departmentId,
|
||||
'relay_id' => $relayId,
|
||||
'deleted_at' => null,
|
||||
], ['id', 'name']);
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$name = trim((string)($row['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function relayLogBindingPayload(array $binding): array
|
||||
{
|
||||
$metadata = isset($binding['metadata']) && is_array($binding['metadata']) ? (array)$binding['metadata'] : [];
|
||||
|
||||
return $this->compactRelayLogArray([
|
||||
'id' => isset($binding['id']) ? (int)$binding['id'] : null,
|
||||
'gateway_id' => isset($binding['gateway_id']) ? (int)$binding['gateway_id'] : null,
|
||||
'department_id' => isset($binding['department_id']) ? (int)$binding['department_id'] : null,
|
||||
'relay_id' => $binding['relay_id'] ?? null,
|
||||
'relay_name' => $this->firstRelayLogString([
|
||||
$metadata['relay_name'] ?? null,
|
||||
$metadata['relay_label'] ?? null,
|
||||
$metadata['name'] ?? null,
|
||||
$metadata['label'] ?? null,
|
||||
]),
|
||||
'device_id' => $binding['device_id'] ?? null,
|
||||
'local_ip' => $binding['local_ip'] ?? null,
|
||||
'channel' => isset($binding['channel']) ? (int)$binding['channel'] : null,
|
||||
|
||||
Reference in New Issue
Block a user