Browse Source

build graph on server, add loader

master
Milan Pässler 11 months ago
parent
commit
f6d7c96c77
8 changed files with 105 additions and 112 deletions
  1. +2
    -2
      client/index.html
  2. +32
    -67
      client/src/index.js
  3. +21
    -0
      client/style.css
  4. +1
    -6
      server/alpine.js
  5. +1
    -6
      server/archlinux.js
  6. +46
    -13
      server/index.js
  7. +1
    -12
      server/nixpkgs.js
  8. +1
    -6
      server/openwrt.js

+ 2
- 2
client/index.html View File

@@ -14,13 +14,13 @@
<div class="bar bar2"></div>
<div class="bar bar3"></div>
</div>
<div id="loading"><div id="spinner"></div></div>
<div id="menu">
<form id="inputs">
<select id="backend">
</select>
<input type="text" id="name">
<input type="submit" id="go" value="Go">
<p class="hidden" id="queued">Queued: <span id="queued-count"></span></p>
</form>
<div class="pusher"></div>
<a id="source-link" href="https://git.pbb.lc/petabyteboy/pkgvis/">
@@ -31,5 +31,5 @@
<canvas id="canvas"></canvas>
<noscript>JavaScript is required to use package dependency viewer</noscript>
</body>
<script type="module" src="main.min.js"></script>
<script type="module" src="main.js"></script>
</html>

+ 32
- 67
client/src/index.js View File

@@ -19,12 +19,13 @@ const view = {
pinchStartScale: 0,
};

const graph = {
let graph = {
nodes: {},
links: []
};
let queued = 0;
let queryNum = 0;

let currentRequestNumber = 0;

let simulation = d3Force.forceSimulation();
let linkForce = d3Force.forceLink([]).id(d => String(d.id));
const canvas = document.getElementById('canvas');
@@ -70,61 +71,6 @@ function nodesAsArray() {
return Object.keys(graph.nodes).map((key) => graph.nodes[key]);
}

function updateQueued(newQueued) {
queued = newQueued;

let queuedP = document.getElementById("queued");
let queuedSpan = document.getElementById("queued-count");

if (queued === 0) {
queuedP.classList.add("hidden");
} else {
queuedP.classList.remove("hidden");
queuedSpan.textContent = queued;
}
}

function addNode(backend, node) {
if (!graph.nodes[node.id]) {
node.weight = Math.min(50, 5 + (node.size / 5000000) || 0);
graph.nodes[node.id] = node;
node.deps.forEach(async (link) => {
let neighbour;
try {
neighbour = await fetchNode(backend, "id", link);
} catch(err) {
return;
}
addNode(backend, neighbour);
graph.links.push({
source: node.id,
target: neighbour.id
});
linkForce.links(graph.links);
simulation.alpha(0.5);
simulation.restart();
});

simulation.alpha(0.5);
simulation.restart();
simulation.nodes(nodesAsArray());
}
updateQueued(queued-1);
}

async function fetchNode(backend, type, name) {
updateQueued(queued+1);
const queryNumAtStart = queryNum;

const node = await fetch(`${apiUri}/${backend}/${type}/${name}`)
.then(resp => resp.json());

if (queryNum !== queryNumAtStart)
throw new Error("");

return node;
}

async function init() {

const backendSelect = document.getElementById("backend");
@@ -149,14 +95,33 @@ async function init() {
document.querySelector('#backend [value="' + backend + '"]').selected = true;
document.getElementById("name").blur();
document.getElementById("name").value = name;
window.location.hash = `#${backend}:${name}`;
queryNum++;
queued = 0;
graph.nodes = {};
graph.links = [];
const node = await fetchNode(backend, "name", name);
addNode(backend, node);
view.selectedNode = node;

graph = {
nodes: {},
links: [],
};
simulation.nodes(nodesAsArray());
linkForce.links(graph.links);

document.getElementById("loading").classList.remove("hidden");
currentRequestNumber++;
const thisRequest = currentRequestNumber;
const newGraph = await fetch(`${apiUri}/${backend}/${name}`)
.then(resp => resp.json());
if (currentRequestNumber !== thisRequest) return;
graph = newGraph;
document.getElementById("loading").classList.add("hidden");

for (let id of Object.keys(graph.nodes)) {
graph.nodes[id].weight = Math.min(50, 5 + (graph.nodes[id].size / 5000000) || 0);
}

simulation.nodes(nodesAsArray());
linkForce.links(graph.links);
simulation.alpha(0.5);
simulation.restart();

view.selectedNode = nodesAsArray().filter(n => n.name === name);
}

document.getElementById("inputs").addEventListener("submit", function(event) {
@@ -164,7 +129,7 @@ async function init() {
const backendSelect = document.getElementById("backend");
const backend = backendSelect[backendSelect.selectedIndex].value;
const name = document.getElementById("name").value;
go(backend, name);
window.location.hash = `#${backend}:${name}`;
return true;
});



+ 21
- 0
client/style.css View File

@@ -93,3 +93,24 @@ canvas {
#checkbox:checked ~ #checkbox-bg .bar3 {
transform: rotate(45deg) translate(-8px, -8px);
}

#loading {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#spinner {
border: 2px solid rgba(255, 255, 255, 0.4);
border-top-color: white;
border-radius: 100%;
height: 30vmin;
width: 30vmin;
animation: spin 1s linear infinite;
}

+ 1
- 6
server/alpine.js View File

@@ -65,12 +65,7 @@ const getPackageLists = async () => {
setInterval(getPackageLists, 24 * 60 * 60 * 1000); // 24 hours
getPackageLists();

const getNode = async (parameters) => {
if (parameters.length < 2) {
throw new HTTPError(404, "Invalid URI");
}

const name = parameters[parameters.length-1];
const getNode = async (type, name) => {
if (!packages[name]) {
throw new HTTPError(404, "Failed to get package info");
}


+ 1
- 6
server/archlinux.js View File

@@ -62,12 +62,7 @@ const getPackageLists = async () => {
setInterval(getPackageLists, 24 * 60 * 60 * 1000); // 24 hours
getPackageLists();

const getNode = async (parameters) => {
if (parameters.length < 2) {
throw new HTTPError(404, "Invalid URI");
}

const name = parameters[parameters.length-1];
const getNode = async (type, name) => {
if (!packages[name]) {
throw new HTTPError(404, "Failed to get package info");
}


+ 46
- 13
server/index.js View File

@@ -5,16 +5,44 @@ const url = require("url");
const fetch = require("node-fetch");
const HTTPError = require("./error");

const listBackends = async () => {
return Object.keys(backends).filter(b => b !== "backends");
};

const backends = {
NixOS: require("./nixpkgs"),
OpenWRT: require("./openwrt"),
ArchLinux: require("./archlinux"),
Alpine: require("./alpine"),
backends: listBackends
};

const addNode = async (graph, backend, node) => {
if (!graph.nodes[node.id]) {
graph.nodes[node.id] = {
...node,
deps: undefined,
}
const pendingRequests = [];
for (let link of node.deps || []) {
let neighbour;
try {
neighbour = await backend("id", link);
} catch(err) {
continue;
}
graph.links.push({
source: node.id,
target: neighbour.id
});
pendingRequests.push(addNode(graph, backend, neighbour));
}
await Promise.all(pendingRequests);
}
};

const buildGraph = async (backend, name) => {
const graph = {
nodes: {},
links: [],
};
await addNode(graph, backend, await backend("name", name));
return graph;
};

http.createServer(async (req, stream) => {
@@ -25,16 +53,21 @@ http.createServer(async (req, stream) => {

let res;
try {
if (parameters.length < 2) {
throw new HTTPError(404, "Invalid URI");
}
if (parameters.length < 3) {
if (parameters.length === 2 && parameters[1] === "backends") {
res = Object.keys(backends);
} else {
throw new HTTPError(404, "Invalid URI");
}
} else {
const backend = backends[parameters[1]];
if (!backend) {
throw new HTTPError(404, "Invalid Backend");
}
const name = parameters[2];

const backend = backends[parameters[1]];
if (!backend) {
throw new HTTPError(404, "Invalid Backend");
res = await buildGraph(backend, name);
}

res = await backend(parameters.splice(2));
} catch(err) {
if (err instanceof HTTPError) {
stream.writeHead(err.code, { "Content-Type": "text/plain" });


+ 1
- 12
server/nixpkgs.js View File

@@ -7,10 +7,6 @@ const HTTPError = require("./error");

let narinfoCache = {};

setInterval(() => {
narinfoCache = {};
}, 24 * 60 * 60 * 1000); // 24 hours

const getNarinfo = async (hash) => {
if (!narinfoCache[hash]) {
narinfoCache[hash] = await fetch(`https://cache.nixos.org/${hash}.narinfo`)
@@ -44,14 +40,7 @@ const getReferences = async (hash) => {
return key.split(" ").map(parseDerivationPath);
};

const getNode = async (parameters) => {
if (parameters.length < 2) {
throw new HTTPError(404, "Invalid URI");
}

const type = parameters[parameters.length-2];
const name = parameters[parameters.length-1];

const getNode = async (type, name) => {
let getStorePathFunc;
if (type === "name") {
getStorePathFunc = getStorePathFromName;


+ 1
- 6
server/openwrt.js View File

@@ -46,12 +46,7 @@ const getPackageLists = async () => {
setInterval(getPackageLists, 24 * 60 * 60 * 1000); // 24 hours
getPackageLists();

const getNode = async (parameters) => {
if (parameters.length < 2) {
throw new HTTPError(404, "Invalid URI");
}

const name = parameters[parameters.length-1];
const getNode = async (type, name) => {
if (!packages[name]) {
throw new HTTPError(404, "Failed to get package info");
}


Loading…
Cancel
Save