getMessage(), $e->status, $e->errorCode); } $clientId = (int)$tenant['client_id']; $out = []; /* MariaDB doc/chunk counts */ try { $db = dbnToolsDb(); $docs = (int)$db->query("SELECT COUNT(*) FROM client_documents WHERE client_id = {$clientId} AND deleted_at IS NULL")->fetchColumn(); $deleted = (int)$db->query("SELECT COUNT(*) FROM client_documents WHERE client_id = {$clientId} AND deleted_at IS NOT NULL")->fetchColumn(); $chunks = 0; try { $chunks = (int)$db->query("SELECT COUNT(*) FROM client_chunks WHERE client_id = {$clientId}")->fetchColumn(); } catch (Throwable $_) {} $out[] = ['key' => 'mariadb', 'label' => 'MariaDB (bnl_admin)', 'value' => "{$docs} docs · {$chunks} chunks · {$deleted} trashed", 'status' => 'ok', 'detail' => 'chloe MySQL — client_documents + client_chunks tables']; } catch (Throwable $e) { $out[] = ['key' => 'mariadb', 'label' => 'MariaDB', 'value' => 'unreachable', 'status' => 'err', 'detail' => $e->getMessage()]; } /* FULLTEXT index presence */ try { $db = dbnToolsDb(); $ft = $db->query("SHOW INDEX FROM client_chunks WHERE Key_name = 'ft_content'")->fetchAll(); $out[] = ['key' => 'fulltext', 'label' => 'MariaDB FULLTEXT (BM25)', 'value' => $ft ? 'present on client_chunks.content' : 'missing', 'status' => $ft ? 'ok' : 'warn', 'detail' => 'Required for hybrid keyword search; migration 007']; } catch (Throwable $e) { $out[] = ['key' => 'fulltext', 'label' => 'MariaDB FULLTEXT', 'value' => '—', 'status' => 'warn', 'detail' => $e->getMessage()]; } /* Qdrant — count vectors for this client via scroll */ try { $ch = curl_init('http://10.0.1.10:6333/collections/bnl_client_chunks/points/count'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 4, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'exact' => true, 'filter' => [ 'must' => [ ['key' => 'client_id', 'match' => ['value' => $clientId]] ] ], ]), ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http === 200 && $body) { $j = json_decode($body, true); $cnt = (int)($j['result']['count'] ?? 0); $out[] = ['key' => 'qdrant', 'label' => 'Qdrant (bnl_client_chunks)', 'value' => $cnt . ' points', 'status' => 'ok', 'detail' => 'Colin Docker @ 10.0.2.10:6333, filtered by client_id']; } else { $out[] = ['key' => 'qdrant', 'label' => 'Qdrant', 'value' => 'http ' . $http, 'status' => 'warn', 'detail' => 'Not reachable from web container; vector search may fall back to MariaDB.']; } } catch (Throwable $e) { $out[] = ['key' => 'qdrant', 'label' => 'Qdrant', 'value' => 'error', 'status' => 'err', 'detail' => $e->getMessage()]; } /* LiteLLM embed endpoint */ try { $ch = curl_init('http://10.0.1.10:4000/v1/models'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 4, CURLOPT_HTTPHEADER => ['Authorization: Bearer sk-bnl-litellm-26xR9mK4qvN3wL8sTj7pB2d'], ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $hasEmbed = $body && strpos($body, 'nomic-embed') !== false; $out[] = ['key' => 'litellm', 'label' => 'LiteLLM (Colin)', 'value' => $http === 200 ? ($hasEmbed ? 'reachable · nomic-embed-text registered' : 'reachable · embed model not found') : ('http ' . $http), 'status' => $http === 200 ? ($hasEmbed ? 'ok' : 'warn') : 'warn', 'detail' => 'http://10.0.1.10:4000 — chunker uses /v1/embeddings']; } catch (Throwable $e) { $out[] = ['key' => 'litellm', 'label' => 'LiteLLM', 'value' => 'error', 'status' => 'err', 'detail' => $e->getMessage()]; } /* FalkorDB nodes for this client */ try { $sock = @stream_socket_client('tcp://10.0.2.10:6379', $errno, $errstr, 2); if ($sock) { $cmd = "GRAPH.QUERY"; $graph = "dbn_client_graph"; $cypher = "MATCH (d:Document {client_id: {$clientId}}) RETURN count(d)"; $payload = "*4\r\n$" . strlen($cmd) . "\r\n{$cmd}\r\n$" . strlen($graph) . "\r\n{$graph}\r\n$" . strlen($cypher) . "\r\n{$cypher}\r\n$8\r\n--compact\r\n"; fwrite($sock, $payload); stream_set_timeout($sock, 2); $resp = @fread($sock, 8192); fclose($sock); $count = 0; if (preg_match('/(\d+)/', (string)$resp, $m)) $count = (int)$m[1]; $out[] = ['key' => 'falkor', 'label' => 'FalkorDB (dbn_client_graph)', 'value' => $count . ' Document nodes', 'status' => 'ok', 'detail' => 'Colin @ 10.0.2.10:6379 — populated during ingest']; } else { $out[] = ['key' => 'falkor', 'label' => 'FalkorDB', 'value' => 'unreachable', 'status' => 'warn', 'detail' => 'Graph features hidden; ingest still works.']; } } catch (Throwable $e) { $out[] = ['key' => 'falkor', 'label' => 'FalkorDB', 'value' => 'error', 'status' => 'warn', 'detail' => $e->getMessage()]; } /* On-disk storage usage */ try { $root = dbnToolsEnv('DBN_TOOLS_UPLOAD_ROOT', '') ?: (is_dir('/home/dobetternorge/uploads') ? '/home/dobetternorge/uploads' : DBN_TOOLS_ROOT . '/uploads'); $clientDir = rtrim($root, '/') . '/' . $clientId; $total = 0; $files = 0; if (is_dir($clientDir)) { $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($clientDir, FilesystemIterator::SKIP_DOTS)); foreach ($it as $f) { if ($f->isFile()) { $total += $f->getSize(); $files++; } } } $human = $total < 1024*1024 ? round($total/1024) . ' KB' : ($total < 1024*1024*1024 ? round($total/1024/1024, 1) . ' MB' : round($total/1024/1024/1024, 2) . ' GB'); $out[] = ['key' => 'storage', 'label' => 'Original file storage', 'value' => $human . ' · ' . $files . ' files', 'status' => is_dir($clientDir) ? 'ok' : 'warn', 'detail' => $clientDir]; } catch (Throwable $e) { $out[] = ['key' => 'storage', 'label' => 'Storage', 'value' => 'error', 'status' => 'warn', 'detail' => $e->getMessage()]; } dbnToolsRespond([ 'ok' => true, 'tenant' => [ 'client_id' => $clientId, 'corpus_id' => (int)$tenant['corpus_id'], 'user_id' => (int)$tenant['client_user_id'], ], 'sections' => $out, ]);