diff --git a/public/components/package/header/header.css b/public/components/package/header/header.css index 9a6e938d..aa0284a1 100644 --- a/public/components/package/header/header.css +++ b/public/components/package/header/header.css @@ -66,6 +66,12 @@ div.package-header>.package-name>.info-menu>a { /** * DESC */ + +div.package-header>.package-description{ + display: flex; + justify-content: space-between; +} + div.package-header>.description { width: 100%; color: #FFF; @@ -74,6 +80,15 @@ div.package-header>.description { margin-top: 7px; } +div.package-header>.package-description>.has-duplicate { + border: none; + background: transparent; + transform: translateY(-4px); + font-size: 18px; + cursor: pointer; + padding: 0; +} + /** * FLAGS */ diff --git a/public/components/package/header/header.js b/public/components/package/header/header.js index cf099b22..9b0e963b 100644 --- a/public/components/package/header/header.js +++ b/public/components/package/header/header.js @@ -1,5 +1,5 @@ // Import Third-party Dependencies -import { getFlagsEmojisInlined } from "@nodesecure/vis-network"; +import { getFlagsEmojisInlined, FLAGS_EMOJIS } from "@nodesecure/vis-network"; // Import Internal Dependencies import * as utils from "../../../common/utils.js"; @@ -109,6 +109,11 @@ export class PackageHeader { flagsDomElement.appendChild(flagFragment); } + // Has Duplicate Button + if (this.#hasDuplicate()) { + this.#renderHasDuplicateBtn(clone); + } + return links; } @@ -244,6 +249,29 @@ export class PackageHeader { return fragment; } + + #hasDuplicate() { + return this.package.dependencyVersion.flags.some((title) => title === "hasDuplicate") && + !window.settings.config.ignore.warnings.has("hasDuplicate") && + "isDuplicated" in FLAGS_EMOJIS; + } + + #renderHasDuplicateBtn(clone) { + const hasDuplicateBtn = utils.createDOMElement("button", + { classList: ["has-duplicate"], text: FLAGS_EMOJIS.isDuplicated }); + + const packagesList = this.nsn.secureDataSet.findPackagesByName(this.package.dependencyVersion.name) + .map(({ name, version }) => `${name}@${version}`); + + hasDuplicateBtn.addEventListener("click", () => { + const nodeIds = [...this.nsn.findNodeIds(new Set(packagesList))]; + this.nsn.highlightMultipleNodes(nodeIds); + window.locker.lock(); + }); + + const packageDescDiv = clone.querySelector(".package-description"); + packageDescDiv.appendChild(hasDuplicateBtn); + } } function createFlagInfoBulle(text, description) { diff --git a/public/components/package/package.html b/public/components/package/package.html index f6115141..5e866e77 100644 --- a/public/components/package/package.html +++ b/public/components/package/package.html @@ -6,7 +6,9 @@

-

+
+

+
diff --git a/workspaces/vis-network/index.js b/workspaces/vis-network/index.js index 78a26ce7..a2f8c3e5 100644 --- a/workspaces/vis-network/index.js +++ b/workspaces/vis-network/index.js @@ -1,13 +1,14 @@ // Import Internal Dependencies import NodeSecureDataSet from "./src/dataset.js"; import NodeSecureNetwork, { NETWORK_OPTIONS } from "./src/network.js"; -import { getJSON, getFlagsEmojisInlined } from "./src/utils.js"; +import { getJSON, getFlagsEmojisInlined, FLAGS_EMOJIS } from "./src/utils.js"; export * from "./src/constants.js"; export { getJSON, getFlagsEmojisInlined, + FLAGS_EMOJIS, NodeSecureDataSet, NodeSecureNetwork, NETWORK_OPTIONS diff --git a/workspaces/vis-network/src/dataset.js b/workspaces/vis-network/src/dataset.js index 5a84bc11..33e080a6 100644 --- a/workspaces/vis-network/src/dataset.js +++ b/workspaces/vis-network/src/dataset.js @@ -228,4 +228,8 @@ export default class NodeSecureDataSet extends EventTarget { isHighlighted(contact) { return this.#highligthedContacts.names.has(contact.name) || this.#highligthedContacts.emails.has(contact.email); } + + findPackagesByName(name) { + return this.packages.filter((pkg) => pkg.name === name); + } } diff --git a/workspaces/vis-network/src/utils.js b/workspaces/vis-network/src/utils.js index df765376..b385f1a8 100644 --- a/workspaces/vis-network/src/utils.js +++ b/workspaces/vis-network/src/utils.js @@ -5,7 +5,7 @@ import { getManifestEmoji } from "@nodesecure/flags/web"; import * as CONSTANTS from "./constants.js"; // CONSTANTS -const kFlagsEmojis = Object.fromEntries(getManifestEmoji()); +export const FLAGS_EMOJIS = Object.fromEntries(getManifestEmoji()); export async function getJSON(path, customHeaders = Object.create(null)) { const headers = { @@ -73,7 +73,7 @@ export function getFlagsEmojisInlined( } // FIX: when scanner resolve to flags ^3.x - const emoji = kFlagsEmojis[title === "hasDuplicate" ? "isDuplicated" : title]; + const emoji = FLAGS_EMOJIS[title === "hasDuplicate" ? "isDuplicated" : title]; return emoji ? [emoji] : []; }) diff --git a/workspaces/vis-network/test/dataset-payload.json b/workspaces/vis-network/test/dataset-payload.json index 46988ad1..52cfa29e 100644 --- a/workspaces/vis-network/test/dataset-payload.json +++ b/workspaces/vis-network/test/dataset-payload.json @@ -64,6 +64,29 @@ ], "name": "pkg2", "version": "1.0.3" + }, + "1.0.4": { + "usedBy": { + "pkg3": "3.4.0" + }, + "flags": [], + "size": 100, + "author": {}, + "warnings": [], + "composition": { + "extensions": [ + ".md", + ".js", + "", + ".json" + ] + }, + "licenses": [], + "uniqueLicenseIds": [ + "Unlicense" + ], + "name": "pkg2", + "version": "1.0.4" } } }, @@ -156,4 +179,4 @@ "warning_02" ], "vulnerabilityStrategy": "npm" -} \ No newline at end of file +} diff --git a/workspaces/vis-network/test/dataset.test.js b/workspaces/vis-network/test/dataset.test.js index c04ae50d..9e9f4fc4 100644 --- a/workspaces/vis-network/test/dataset.test.js +++ b/workspaces/vis-network/test/dataset.test.js @@ -97,3 +97,39 @@ test("NodeSecureDataSet.build", () => { assert.equal(builtData.nodes.length, 2, "should have 2 nodes"); assert.equal(builtData.edges.length, 3, "should have 3 edges"); }); + +test("NodeSecureDataSet.findPackagesByName should have no packages when no name matches", async() => { + const nsDataSet = new NodeSecureDataSet(); + await nsDataSet.init(dataSetPayload); + + assert.equal(nsDataSet.findPackagesByName("unknown").length, 0, "should have no packages"); +}); + +test("NodeSecureDataSet.findPackagesByName should have packages when name matches", async() => { + const nsDataSet = new NodeSecureDataSet(); + await nsDataSet.init(dataSetPayload); + const packages = nsDataSet.findPackagesByName("pkg2"); + + const expectedPackages = [ + { + id: undefined, + name: "pkg2", + version: "1.0.3", + hasWarnings: false, + flags: "", + links: undefined, + isFriendly: 0 + }, + { + id: undefined, + name: "pkg2", + version: "1.0.4", + hasWarnings: false, + flags: "", + links: undefined, + isFriendly: 0 + } + ]; + + assert.deepEqual(packages, expectedPackages, "should all versions by name"); +});