Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lxdex #500

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft

Lxdex #500

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions lxdex/buildCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

require __DIR__ . "/../vendor/autoload.php";

$env = (new Dotenv\Dotenv(__DIR__ . "/../"))->load();

$container = (new \DI\ContainerBuilder())->build();
$generateCert = $container->make("dhope0000\LXDClient\Tools\Hosts\GenerateCert");

$hosts = $container->make("dhope0000\LXDClient\Model\Hosts\HostList")->getOnlineHostsWithDetails();

$hasExtension = new dhope0000\LXDClient\Tools\Hosts\HasExtension();

$output = [];

$recursionLevel = 2;

function labelArrays(array $items, string $key)
{
$out = [];
foreach ($items as $item) {
$out[$item[$key]] = $item;
}
return $out;
}


foreach ($hosts as $host) {
$supportsProjects = $hasExtension->checkWithHost($host, "projects");
$output[$host->getHostId()] = [];
$allProjects = [["name"=>"default", "config"=>[]]];

if ($supportsProjects) {
$allProjects = $host->projects->all(2);
}

foreach ($allProjects as $project) {
$projectName = $project["name"];

$output[$host->getHostId()][$projectName] = [
"instances"=>[],
"networks"=>[],
"storage"=>[],
"images"=>[],
"profiles"=>[]
];

if ($supportsProjects) {
$host->setProject($projectName);
}

$output[$host->getHostId()][$projectName]["instances"] = labelArrays($host->instances->all($recursionLevel), "name");

if ($projectName == "default" || (isset($project["features.networks"]) && $project["features.networks"] === "true")) {
$output[$host->getHostId()][$projectName]["networks"] = labelArrays($host->networks->all($recursionLevel), "name");
}

if ($projectName == "default" || (isset($project["features.storage"]) && $project["features.storage"] === "true")) {
$output[$host->getHostId()][$projectName]["storage"] = labelArrays($host->storage->all($recursionLevel), "name");
}

if ($projectName == "default" || (isset($project["features.images"]) && $project["features.images"] === "true")) {
$output[$host->getHostId()][$projectName]["images"] = labelArrays($host->images->all($recursionLevel), "fingerprint");
}

if ($projectName == "default" || (isset($project["features.profiles"]) && $project["features.profiles"] === "true")) {
$output[$host->getHostId()][$projectName]["profiles"] = labelArrays($host->profiles->all($recursionLevel), "name");
}
}
}

file_put_contents(__DIR__ . "/cache.json", json_encode($output));
116 changes: 116 additions & 0 deletions lxdex/buildIndex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

use Symfony\Component\Yaml\Yaml;

require __DIR__ . "/../vendor/autoload.php";

// NOTE So this takes over 1 second on somewhere between 4000-8000 hosts

$output = json_decode(file_get_contents(__DIR__ . "/cache.json"), true);

// Copy and paste the below line to simulate more host data to parse
// $output = array_merge($output, array_values($output));
// var_dump(count($output));

// Inspired by https://stackoverflow.com/a/28473131/4008082
function buildIndex(array $input, array $indexableKeys, array &$index, string $currentKey = '', array &$uniqueEntities = [], int &$idChain = 0) :void
{
foreach ($input as $key=>$value) {
foreach ($indexableKeys as $searchKey => $searchCallback) {
$i = preg_match($searchKey, ($currentKey . "[$key]"));

if ($i !== 0) {
if (!is_array($value)) {
if (!isset($uniqueEntities[$currentKey . "[$key]"])) {
$uniqueEntities[$currentKey . "[$key]"] = $idChain;
$idChain = $idChain + 1;
}

if (is_callable($searchCallback)) {
$searchCallback($key, ($currentKey . "[$key]"), $value, $index);
} else {
$v = strtolower($value);

if ($value == "") {
continue;
}

if (!isset($index[$v])) {
$index[$v] = [];
}

$index[$v][] = $uniqueEntities[$currentKey . "[$key]"];
// $index[$v][] = $currentKey . "[$key]";
}
} else {
throw new \Exception("Indexing array keys not supported yet", 1);
}
}
}

if (is_array($value)) {
buildIndex($value, $indexableKeys, $index, $currentKey . "[$key]", $uniqueEntities, $idChain);
}
}
}

$valueToListFunc = function ($key, $path, $value, &$index) {
if ($value == "") {
return;
}
if (!isset($index[$value])) {
$index[$value] = [];
}
if (!isset($index[$value][$key])) {
$index[$value][$key] = [];
}
$index[$value][$key][] = $path;
};

$indexableKeys = [
// Status of running instances
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[state\]\[status\]/m'=>null,
// Require cuda
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[config\]\[nvidia\.require\.cuda\]/m'=>$valueToListFunc,
// Require cuda
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[config\]\[nvidia\.runtime\]/m'=>$valueToListFunc,
// IP addresses
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[state\]\[network\]\[([^]]+)\]\[addresses\]\[([^]]+)\]\[address\]/m'=>null,
// Mac addresses
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[state\]\[network\]\[([^]]+)\]\[hwaddr\]/m'=>null,
// Target folder paths on instance specific mounts
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[devices\]\[share\]\[path\]/m'=>null,
// Source folder paths on instance specific mounts
'/\[([^]]+)\]\[([^]]+)\]\[instances\]\[([^]]+)\]\[devices\]\[share\]\[source\]/m'=>null,
// All image properties
'/\[([^]]+)\]\[([^]]+)\]\[images\]\[([^]]+)\]\[properties\]\[([^]]+)\]/m'=>null,
// All image fingerprints
'/\[([^]]+)\]\[([^]]+)\]\[images\]\[([^]]+)\]\[fingerprint\]/m'=>null,
// Any Top Level Name
'/^\[([^]]+)\]\[([^]]+)\]\[([^]]+)\]\[([^]]+)\]\[name\]/'=>null,
// Any Operating System Names
'/\[([^]]+)\]\[([^]]+)\]\[([^]]+)\]\[([^]]+)\]\[(properties|config)\]\[(os|image\.os)\]/'=>null,
// Cloud Config Data
'/\[([^]]+)\]\[([^]]+)\]\[([^]]+)\]\[([^]]+)\]\[config\]\[\user\.user-data\]/'=>function ($key, $path, $value, &$index) {
try {
$x = Yaml::parse($value);
if (isset($x["packages"])) {
foreach ($x["packages"] as $package) {
if (!isset($index[$package])) {
$index[$package] = [];
}
$index[$package][] = $path;
}
}
} catch (\Throwable $e) { // Slienty ingore user-data we cant parse as yaml
}
},
// All storage drivers
'/\[([^]]+)\]\[([^]]+)\]\[storage\]\[([^]]+)\]\[driver\]/m'=>null,
];

$index = [];
$uniqueEntities = [];
$idChain = 0;
buildIndex($output, $indexableKeys, $index, '', $uniqueEntities, $idChain);
file_put_contents(__DIR__ . "/index.json", json_encode(["dataIndex"=>$index, "entityIndex"=>$entityIndex]));
94 changes: 94 additions & 0 deletions src/classes/Controllers/Search/SearchIndexController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace dhope0000\LXDClient\Controllers\Search;

use Symfony\Component\Routing\Annotation\Route;

class SearchIndexController
{
/**
* @Route("/api/search/fuzzy", methods={"POST"}, name="Fuzzy Search")
*/
public function get(string $search)
{
// NOTE So this takes over 1 second on somewhere between 4000-8000 hosts
$index = json_decode(file_get_contents(__DIR__ . "/../../../../lxdex/index.json"), true);

$entityIndex = $index["entityIndex"];
$index = $index["dataIndex"];

$searchParts = explode(" ", $search);
$results = [];

foreach ($searchParts as $searchPart) {
// TODO check if searching properties split by ":"
foreach ($index as $indexKey => $usedBy) {
if (is_numeric(implode(array_keys($usedBy)))) {
$this->checkMatch($searchPart, $indexKey, $usedBy, $results, $entityIndex);
} else {
foreach ($usedBy as $subIndexKey=>$xyz) {
$this->checkMatch($searchPart, $subIndexKey, $xyz, $results, $entityIndex);
}
}
}
}

foreach ($results as $index => $xyz) {
if (count($xyz["searchMatches"]) >= count($searchParts)) {
continue;
}
unset($results[$index]);
}
//TODO Filter results by user access
return $this->formatResults($results);
}

private function formatResults($results)
{
$output = [];
foreach ($results as $item => $matches) {
$parts = explode(",", $item);
$output[] = [
"hostId"=>$parts[0],
"project"=>$parts[1],
"entity"=>$parts[2],
"name"=>$parts[3],
"matches"=>$matches
];
}
usort($output, function ($a, $b) {
return $a["entity"] > $b["entity"] ? 1 : -1;
});
return $output;
}


private function checkMatch($search, $haystack, array $toExtract, &$results, $entityIndex)
{
if (strpos($haystack, $search) !== false) {
foreach ($toExtract as $entity) {
if (!isset($entityIndex[$entity])) {
continue; // TODO Really bad this shouldn't happen
}
$y = $entityIndex[$entity];
$x = str_replace("[", "", $y);
$parts = explode("]", $x);
$entityName = implode(array_slice($parts, 0, 4), ",");
$entityProp = implode(array_slice($parts, 4, count($parts)), ",");
// $entityName = $x;

if (!isset($results[$entityName])) {
$results[$entityName] = [
"searchMatches"=>[],
"matches"=>[]
];
}
if (!isset($results[$entityName]["searchMatches"][$search])) {
$results[$entityName]["searchMatches"][$search] = 0;
}
$results[$entityName]["searchMatches"][$search]++;
$results[$entityName]["matches"][$entityProp] = $haystack;
}
}
}
}
83 changes: 47 additions & 36 deletions src/views/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,44 +429,54 @@
});

$(document).on("click", "#openSearch", function(){
$.confirm({
title: `Search`,
content: `
<div class="mb-2">
<label>IP Address IPV4/IPV6</label>
<input class="form-control" name="ip" />
</div>
`,
buttons: {
cancel: {
btnClass: "btn btn-secondary",
text: "cancel"
},
search: {
btnClass: "btn btn-success",
text: "Search",
action: function(){
let x = {
ip: this.$content.find("input[name=ip]").val()
}

ajaxRequest(globalUrls.networks.tools.findIpAddress, x, (data)=>{
data = makeToastr(data);
if(data.state == "error"){
return false;
}
if(data.result == false){
makeToastr({state: "error", message: "Couldn't find instance"})
return false;
}
router.navigate(`/instance/${hostIdOrAliasForUrl(data.result.alias, data.result.hostId)}/${data.result.container}`);
});
}
}
$("#modal-search").modal("show");
// $.confirm({
// title: `Search`,
// content: `
// <div class="mb-2">
// <label>IP Address IPV4/IPV6</label>
// <input class="form-control" name="ip" />
// </div>
// `,
// buttons: {
// cancel: {
// btnClass: "btn btn-secondary",
// text: "cancel"
// },
// search: {
// btnClass: "btn btn-success",
// text: "Search",
// action: function(){
// let x = {
// ip: this.$content.find("input[name=ip]").val()
// }
//
// ajaxRequest(globalUrls.networks.tools.findIpAddress, x, (data)=>{
// data = makeToastr(data);
// if(data.state == "error"){
// return false;
// }
// if(data.result == false){
// makeToastr({state: "error", message: "Couldn't find instance"})
// return false;
// }
// router.navigate(`/instance/${hostIdOrAliasForUrl(data.result.alias, data.result.hostId)}/${data.result.container}`);
// });
// }
// }
// }
// });
});
$(window).bind('keydown', function(event) {
if (event.ctrlKey || event.metaKey) {
switch (String.fromCharCode(event.which).toLowerCase()) {
case 's':
event.preventDefault();
$("#navbarSearchInput").focus()
break;
}
});
}
});

</script>
</head>
<form style="display: none;" method="POST" id="downloadContainerFileForm" action="/api/Instances/Files/GetPathController/get">
Expand Down Expand Up @@ -848,5 +858,6 @@ function makeProjectDropDown(member){
<?php
require_once __DIR__ . "/modals/images/import.php";
require_once __DIR__ . "/modals/projects/projectAccess.php";
require_once __DIR__ . "/modals/search/search.php";
?>
</html>
Loading