From 066618e4df41a5e108e5455117ba8623ce373da1 Mon Sep 17 00:00:00 2001 From: Pierre Demailly Date: Thu, 9 Jan 2025 20:54:55 +0100 Subject: [PATCH] feat: add standalone mode for local scans --- public/components/navigation/navigation.js | 17 +++++++++++- public/components/searchbar/searchbar.css | 2 ++ public/core/search-nav.js | 30 ++++++++++++++++++++-- public/main.js | 19 ++++++++++++-- src/commands/scanner.js | 13 ++++++++-- src/http-server/cache.js | 22 ++++++++++++++++ src/http-server/config.js | 20 +++++++++++++-- src/http-server/endpoints/data.js | 8 ++++++ src/http-server/index.js | 4 +++ src/http-server/websocket/init.js | 4 +++ workspaces/vis-network/src/dataset.js | 4 ++- 11 files changed, 133 insertions(+), 10 deletions(-) diff --git a/public/components/navigation/navigation.js b/public/components/navigation/navigation.js index 4dead174..ba9c7fde 100644 --- a/public/components/navigation/navigation.js +++ b/public/components/navigation/navigation.js @@ -75,7 +75,7 @@ export class ViewNavigation { const searchbar = document.getElementById("searchbar"); if (searchbar) { - searchbar.style.display = menuName === "network--view" ? "flex" : "none"; + searchbar.style.display = menuName === "network--view" ? "flex" : "none"; } this.activeMenu = selectedNav; @@ -114,4 +114,19 @@ export class ViewNavigation { this.setNewActiveMenu(selectedNav); } } + + hideMenu(menuName, options = {}) { + const { navigateAway = false } = options; + + const menu = this.menus.get(menuName); + if (!menu) { + return; + } + + menu.classList.add("hidden"); + + if (navigateAway && this.activeMenu.isEqualNode(menu)) { + this.setNavByName("network--view"); + } + } } diff --git a/public/components/searchbar/searchbar.css b/public/components/searchbar/searchbar.css index cddcc483..398be8bf 100644 --- a/public/components/searchbar/searchbar.css +++ b/public/components/searchbar/searchbar.css @@ -206,6 +206,7 @@ div.search-result-pannel .package+.package { height: 30px; left: 50px; padding-left: 20px; + min-width: 455px; max-width: calc(100vw - 70px); box-sizing: border-box; background: var(--primary); @@ -227,6 +228,7 @@ div.search-result-pannel .package+.package { display: flex; max-width: calc(100vw - 70px - 264px); background: var(--primary); + margin-left: auto; } #search-nav .packages>.package { diff --git a/public/core/search-nav.js b/public/core/search-nav.js index 58fe08a1..fcaf0ad6 100644 --- a/public/core/search-nav.js +++ b/public/core/search-nav.js @@ -3,14 +3,20 @@ import { createDOMElement, parseNpmSpec } from "../common/utils"; import { SearchBar } from "../components/searchbar/searchbar"; export function initSearchNav(data, options) { - const { initFromZero = true, searchOptions = null } = options; + const { initFromZero = true, searchOptions = null, initSinglePackage } = options; const searchNavElement = document.getElementById("search-nav"); if (!searchNavElement) { throw new Error("Unable to found search navigation"); } - if (initFromZero) { + if (initSinglePackage) { + searchNavElement.innerHTML = ""; + searchNavElement.appendChild( + initSingleNavigation(initSinglePackage) + ); + } + else if (initFromZero) { searchNavElement.innerHTML = ""; searchNavElement.appendChild( initPackagesNavigation(data) @@ -122,3 +128,23 @@ function renderPackageRemoveButton(packageName, options) { return removeButton; } + +function initSingleNavigation(packageName) { + const fragment = document.createDocumentFragment(); + const container = createDOMElement("div", { + classList: ["packages"] + }); + + const pkgElement = createDOMElement("div", { + classList: ["package"], + childs: [ + createDOMElement("p", { text: packageName }) + ] + }); + pkgElement.dataset.name = packageName; + + container.appendChild(pkgElement); + fragment.append(container); + + return fragment; +} diff --git a/public/main.js b/public/main.js index 774cda3c..b945c3ea 100644 --- a/public/main.js +++ b/public/main.js @@ -31,9 +31,13 @@ document.addEventListener("DOMContentLoaded", async() => { window.scannedPackageCache = []; window.locker = null; window.popup = new Popup(); + window.navigation = new ViewNavigation(); window.settings = await new Settings().fetchUserConfig(); + if (window.settings.config.standalone) { + console.log(`[INFO] Standalone mode activated`); + window.navigation.hideMenu("search--view", { navigateAway: true }); + } window.i18n = await new i18n().fetch(); - window.navigation = new ViewNavigation(); window.wiki = new Wiki(); await init(); @@ -109,7 +113,18 @@ async function init(options = {}) { window.locker = new Locker(nsn); window.legend = new Legend({ show: window.settings.config.showFriendlyDependencies }); new HomeView(secureDataSet, nsn); - searchview ??= new SearchView(secureDataSet, nsn); + if (window.settings.config.standalone) { + window.activePackage = secureDataSet.data.rootDependencyName; + initSearchNav(void 0, { + initSinglePackage: secureDataSet.data.rootDependencyName, + searchOptions: { + nsn, secureDataSet + } + }); + } + else { + searchview ??= new SearchView(secureDataSet, nsn); + } window.addEventListener("package-info-closed", () => { window.networkNav.currentNodeParams = null; diff --git a/src/commands/scanner.js b/src/commands/scanner.js index 01536dba..556b7be3 100644 --- a/src/commands/scanner.js +++ b/src/commands/scanner.js @@ -13,6 +13,7 @@ import * as Scanner from "@nodesecure/scanner"; // Import Internal Dependencies import * as http from "./http.js"; +import { appCache } from "../http-server/cache.js"; export async function auto(spec, options) { const { keep, ...commandOptions } = options; @@ -59,7 +60,7 @@ export async function cwd(options) { initLogger(void 0, !silent) ); - return logAndWrite(payload, output); + return logAndWrite(payload, output, { lockUi: true }); } export async function from(spec, options) { @@ -153,7 +154,11 @@ function initLogger(spec, verbose = true) { return logger; } -function logAndWrite(payload, output = "nsecure-result") { +function logAndWrite(payload, output = "nsecure-result", options = {}) { + const { + lockUi = false + } = options; + if (payload === null) { console.log(i18n.getTokenSync("cli.no_dep_to_proceed")); @@ -179,5 +184,9 @@ function logAndWrite(payload, output = "nsecure-result") { console.log(kleur.white().bold(i18n.getTokenSync("cli.successfully_written_json", kleur.green().bold(filePath)))); console.log(""); + if (lockUi) { + appCache.setStandalonePayload(payload); + } + return filePath; } diff --git a/src/http-server/cache.js b/src/http-server/cache.js index c2b9a753..f8324fdf 100644 --- a/src/http-server/cache.js +++ b/src/http-server/cache.js @@ -19,6 +19,13 @@ export const CACHE_PATH = path.join(os.tmpdir(), "nsecure-cli"); export const DEFAULT_PAYLOAD_PATH = path.join(process.cwd(), "nsecure-result.json"); class _AppCache { + /** + * - `undefined`: unknown + * - `true`: standalone + * - `false`: not standalone + */ + isStandalone; + constructor() { fs.mkdirSync(kPayloadsPath, { recursive: true }); } @@ -127,6 +134,21 @@ class _AppCache { root }; } + + async setStandalonePayload(payload) { + await cacache.put(CACHE_PATH, "standalone", JSON.stringify(payload)); + } + + async getStandalonePayload() { + try { + const { data } = await cacache.get(CACHE_PATH, "standalone"); + + return JSON.parse(data.toString()); + } + catch { + return null; + } + } } export const appCache = new _AppCache(); diff --git a/src/http-server/config.js b/src/http-server/config.js index 4f0998d5..42371802 100644 --- a/src/http-server/config.js +++ b/src/http-server/config.js @@ -1,5 +1,8 @@ +// Import Node.js Dependencies +import fs from "node:fs"; + // Import Internal Dependencies -import { appCache } from "./cache.js"; +import { appCache, DEFAULT_PAYLOAD_PATH } from "./cache.js"; import { logger } from "./logger.js"; // CONSTANTS @@ -12,6 +15,17 @@ export async function get() { try { const config = await appCache.getConfig(); + let standalone = false; + const standalonePayload = await appCache.getStandalonePayload(); + if (standalonePayload && fs.existsSync(DEFAULT_PAYLOAD_PATH)) { + const localPayload = JSON.parse(fs.readFileSync(DEFAULT_PAYLOAD_PATH, "utf-8")); + if (localPayload.id === standalonePayload.id) { + standalone = true; + } + } + appCache.isStandalone = standalone; + Object.assign(config, { standalone }); + const { defaultPackageMenu, ignore: { @@ -19,7 +33,9 @@ export async function get() { warnings } = {} } = config; - logger.info(`[config|get](defaultPackageMenu: ${defaultPackageMenu}|ignore-flag: ${flags}|ignore-warnings: ${warnings})`); + logger.info( + `[config|get](defaultPackageMenu: ${defaultPackageMenu}|flags: ${flags}|warnings: ${warnings}|standalone: ${standalone})` + ); return config; } diff --git a/src/http-server/endpoints/data.js b/src/http-server/endpoints/data.js index 02194a86..344f303f 100644 --- a/src/http-server/endpoints/data.js +++ b/src/http-server/endpoints/data.js @@ -13,6 +13,14 @@ import { logger } from "../logger.js"; const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json"); export async function get(req, res) { + if (appCache.isStandalone) { + logger.info("[data|get] standalone mode"); + const payload = await appCache.getStandalonePayload(); + send(res, 200, payload); + + return; + } + try { const { current, lru } = await appCache.payloadsList(); logger.info(`[data|get](current: ${current})`); diff --git a/src/http-server/index.js b/src/http-server/index.js index bab1b82d..ae240a31 100644 --- a/src/http-server/index.js +++ b/src/http-server/index.js @@ -22,6 +22,7 @@ import * as report from "./endpoints/report.js"; import * as middleware from "./middleware.js"; import * as wsHandlers from "./websocket/index.js"; import { logger } from "./logger.js"; +import { appCache } from "./cache.js"; export function buildServer(dataFilePath, options = {}) { const httpConfigPort = typeof options.port === "number" ? options.port : 0; @@ -65,6 +66,9 @@ export function buildServer(dataFilePath, options = {}) { if (enableWS) { const websocket = new WebSocketServer({ port: 1338 }); websocket.on("connection", async(socket) => { + if (appCache.isStandalone) { + return; + } socket.on("message", async(rawMessage) => { const message = JSON.parse(rawMessage); logger.info(`[ws](message: ${JSON.stringify(message)})`); diff --git a/src/http-server/websocket/init.js b/src/http-server/websocket/init.js index fe3a84ac..db1259ca 100644 --- a/src/http-server/websocket/init.js +++ b/src/http-server/websocket/init.js @@ -3,6 +3,10 @@ import { appCache } from "../cache.js"; import { logger } from "../logger.js"; export async function init(socket, lock = false) { + if (appCache.isStandalone) { + return; + } + try { const { current, lru, older, root } = await appCache.payloadsList(); logger.info(`[ws|init](lru: ${lru}|older: ${older}|current: ${current}|root: ${root})`); diff --git a/workspaces/vis-network/src/dataset.js b/workspaces/vis-network/src/dataset.js index 82959536..9f9ebb67 100644 --- a/workspaces/vis-network/src/dataset.js +++ b/workspaces/vis-network/src/dataset.js @@ -176,7 +176,9 @@ export default class NodeSecureDataSet extends EventTarget { if (author === null) { return; } - const contributor = contributors.find((contributor) => contributor.email === author.email && contributor.npmAvatar !== null); + const contributor = contributors.find( + (contributor) => contributor?.email === author.email && contributor?.npmAvatar !== null + ); if (this.authors.has(author.name)) { this.authors.get(author.name).packages.add(spec);