|
1013 | 1013 | } |
1014 | 1014 | }, "Ref"))), /*#__PURE__*/React.createElement("tbody", null, paged.map((row, i) => { |
1015 | 1015 | const sv = SV[row.severity] || 'MEDIUM'; |
| 1016 | + const isExpanded = expandedId === row.id; |
| 1017 | + const toggleExpand = () => setExpandedId(prev => prev === row.id ? null : row.id); |
1016 | 1018 | return /*#__PURE__*/React.createElement("tr", { |
1017 | 1019 | key: row.id + '-' + i, |
1018 | | - onMouseEnter: e => e.currentTarget.style.background = '#0d1117', |
1019 | | - onMouseLeave: e => e.currentTarget.style.background = 'transparent' |
| 1020 | + onClick: toggleExpand, |
| 1021 | + onMouseEnter: e => { if (!isExpanded) e.currentTarget.style.background = '#0d1117'; }, |
| 1022 | + onMouseLeave: e => { if (!isExpanded) e.currentTarget.style.background = 'transparent'; }, |
| 1023 | + style: { |
| 1024 | + cursor: 'pointer', |
| 1025 | + background: isExpanded ? '#10151d' : 'transparent', |
| 1026 | + transition: 'background 0.15s ease' |
| 1027 | + } |
1020 | 1028 | }, /*#__PURE__*/React.createElement("td", { |
1021 | 1029 | style: { |
1022 | 1030 | padding: '7px 12px', |
1023 | | - maxWidth: 200, |
1024 | | - overflow: 'hidden', |
1025 | | - textOverflow: 'ellipsis', |
1026 | | - whiteSpace: 'nowrap', |
| 1031 | + maxWidth: isExpanded ? 'none' : 200, |
| 1032 | + overflow: isExpanded ? 'visible' : 'hidden', |
| 1033 | + textOverflow: isExpanded ? 'clip' : 'ellipsis', |
| 1034 | + whiteSpace: isExpanded ? 'normal' : 'nowrap', |
| 1035 | + wordBreak: isExpanded ? 'break-word' : 'normal', |
1027 | 1036 | color: '#c8ccd4' |
1028 | 1037 | } |
1029 | 1038 | }, row.name || '-'), /*#__PURE__*/React.createElement("td", { |
|
1034 | 1043 | } |
1035 | 1044 | }, /*#__PURE__*/React.createElement("span", { |
1036 | 1045 | title: 'Click to copy: ' + row.id + (row.sha256 ? '\nSHA256: ' + row.sha256 : ''), |
1037 | | - onClick: () => { |
| 1046 | + onClick: e => { |
| 1047 | + e.stopPropagation(); |
1038 | 1048 | navigator.clipboard && navigator.clipboard.writeText(row.id); |
1039 | 1049 | setExpandedId(prev => prev === row.id ? null : row.id); |
1040 | 1050 | setCopiedId(row.id); |
|
1043 | 1053 | style: { |
1044 | 1054 | cursor: 'pointer', |
1045 | 1055 | fontFamily: 'monospace', |
1046 | | - background: expandedId === row.id ? '#1c2333' : 'transparent', |
1047 | | - padding: expandedId === row.id ? '2px 6px' : '0', |
| 1056 | + background: isExpanded ? '#1c2333' : 'transparent', |
| 1057 | + padding: isExpanded ? '2px 6px' : '0', |
1048 | 1058 | borderRadius: 4, |
1049 | | - border: expandedId === row.id ? '1px solid #30363d' : '1px solid transparent', |
| 1059 | + border: isExpanded ? '1px solid #30363d' : '1px solid transparent', |
1050 | 1060 | display: 'inline-block', |
1051 | | - wordBreak: expandedId === row.id ? 'break-all' : 'normal', |
1052 | | - whiteSpace: expandedId === row.id ? 'normal' : 'nowrap', |
| 1061 | + wordBreak: isExpanded ? 'break-all' : 'normal', |
| 1062 | + whiteSpace: isExpanded ? 'normal' : 'nowrap', |
1053 | 1063 | transition: 'all 0.15s ease' |
1054 | 1064 | } |
1055 | | - }, expandedId === row.id ? row.id : (row.id.length > 24 ? row.id.slice(0, 24) + '\u2026' : row.id), |
| 1065 | + }, isExpanded ? row.id : (row.id.length > 24 ? row.id.slice(0, 24) + '\u2026' : row.id), |
1056 | 1066 | copiedId === row.id && /*#__PURE__*/React.createElement("span", { |
1057 | 1067 | style: { |
1058 | 1068 | marginLeft: 6, |
1059 | 1069 | color: '#3fb950', |
1060 | 1070 | fontSize: 10, |
1061 | 1071 | fontFamily: 'sans-serif' |
1062 | 1072 | } |
1063 | | - }, '\u2713 copied'))), /*#__PURE__*/React.createElement("td", { |
| 1073 | + }, '\u2713 copied')), |
| 1074 | + isExpanded && row.sha256 && /*#__PURE__*/React.createElement("div", { |
| 1075 | + style: { |
| 1076 | + marginTop: 6, |
| 1077 | + fontSize: 10, |
| 1078 | + color: '#484f58', |
| 1079 | + fontFamily: 'monospace', |
| 1080 | + wordBreak: 'break-all', |
| 1081 | + lineHeight: 1.4 |
| 1082 | + } |
| 1083 | + }, /*#__PURE__*/React.createElement("span", { |
| 1084 | + style: { color: '#6e7681', marginRight: 6 } |
| 1085 | + }, "SHA256:"), row.sha256)), /*#__PURE__*/React.createElement("td", { |
1064 | 1086 | style: { |
1065 | 1087 | padding: '7px 12px' |
1066 | 1088 | } |
|
1080 | 1102 | }, sv)), /*#__PURE__*/React.createElement("td", { |
1081 | 1103 | style: { |
1082 | 1104 | padding: '7px 12px', |
1083 | | - maxWidth: 240, |
1084 | | - overflow: 'hidden', |
1085 | | - textOverflow: 'ellipsis', |
1086 | | - whiteSpace: 'nowrap', |
| 1105 | + maxWidth: isExpanded ? 'none' : 240, |
| 1106 | + minWidth: isExpanded ? 240 : 0, |
| 1107 | + overflow: isExpanded ? 'visible' : 'hidden', |
| 1108 | + textOverflow: isExpanded ? 'clip' : 'ellipsis', |
| 1109 | + whiteSpace: isExpanded ? 'normal' : 'nowrap', |
| 1110 | + wordBreak: isExpanded ? 'break-word' : 'normal', |
1087 | 1111 | color: '#6e7681', |
1088 | | - fontSize: 11 |
| 1112 | + fontSize: 11, |
| 1113 | + lineHeight: isExpanded ? 1.55 : 'normal' |
1089 | 1114 | } |
1090 | 1115 | }, row.comment), /*#__PURE__*/React.createElement("td", { |
1091 | 1116 | style: { |
|
1095 | 1120 | href: row.link, |
1096 | 1121 | target: "_blank", |
1097 | 1122 | rel: "noopener noreferrer", |
| 1123 | + onClick: e => e.stopPropagation(), |
1098 | 1124 | style: { |
1099 | 1125 | color: '#58a6ff', |
1100 | 1126 | textDecoration: 'none', |
|
1509 | 1535 | } |
1510 | 1536 | }, "+ Contribute extension"); |
1511 | 1537 | } |
1512 | | -function ExtensionChecker() { |
1513 | | - const [subTab, setSubTab] = useState('check'); |
| 1538 | +function ExtensionChecker({ subTab, setSubTab }) { |
1514 | 1539 | const [extId, setExtId] = useState(''); |
1515 | 1540 | const [manifest, setManifest] = useState(''); |
1516 | 1541 | const [result, setResult] = useState(null); |
|
2450 | 2475 | type: "info" |
2451 | 2476 | }, "Permission-based blocking stops any extension requesting dangerous capabilities, regardless of whether it is in the malicious feed. Combine with a blocklist for defense in depth."))); |
2452 | 2477 | } |
| 2478 | +const TAB_SUBS = { |
| 2479 | + dashboard: [], |
| 2480 | + feeds: ['malicious', 'sensitive', 'download'], |
| 2481 | + checker: ['check', 'analyze', 'inventory'], |
| 2482 | + policy: [], |
| 2483 | + guide: ['traces', 'remediation'] |
| 2484 | +}; |
| 2485 | +const TAB_IDS = Object.keys(TAB_SUBS); |
| 2486 | +function parseHash() { |
| 2487 | + const raw = (window.location.hash || '').replace(/^#\/?/, ''); |
| 2488 | + if (!raw) return { tab: 'dashboard', sub: null }; |
| 2489 | + const parts = raw.split('/').filter(Boolean); |
| 2490 | + const tab = TAB_IDS.includes(parts[0]) ? parts[0] : 'dashboard'; |
| 2491 | + const subs = TAB_SUBS[tab]; |
| 2492 | + const sub = parts[1] && subs.includes(parts[1]) ? parts[1] : null; |
| 2493 | + return { tab, sub }; |
| 2494 | +} |
| 2495 | +function buildHash(tab, sub) { |
| 2496 | + return sub ? '#' + tab + '/' + sub : '#' + tab; |
| 2497 | +} |
| 2498 | +function pushHash(tab, sub) { |
| 2499 | + const h = buildHash(tab, sub); |
| 2500 | + if (window.location.hash !== h) { |
| 2501 | + window.history.pushState(null, '', h); |
| 2502 | + } |
| 2503 | +} |
2453 | 2504 | function App() { |
2454 | | - const [tab, setTab] = useState('dashboard'); |
2455 | | - const [feedSub, setFeedSub] = useState('malicious'); |
2456 | | - const [guidePanel, setGuidePanel] = useState('traces'); |
| 2505 | + const initial = parseHash(); |
| 2506 | + const [tab, setTab] = useState(initial.tab); |
| 2507 | + const [feedSub, setFeedSub] = useState(initial.tab === 'feeds' && initial.sub ? initial.sub : 'malicious'); |
| 2508 | + const [checkerSub, setCheckerSub] = useState(initial.tab === 'checker' && initial.sub ? initial.sub : 'check'); |
| 2509 | + const [guidePanel, setGuidePanel] = useState(initial.tab === 'guide' && initial.sub ? initial.sub : 'traces'); |
| 2510 | + |
| 2511 | + // Ensure URL reflects initial state (e.g. empty hash -> #dashboard, or #feeds -> #feeds/malicious) |
| 2512 | + React.useEffect(() => { |
| 2513 | + const currentSub = |
| 2514 | + tab === 'feeds' ? feedSub : |
| 2515 | + tab === 'checker' ? checkerSub : |
| 2516 | + tab === 'guide' ? guidePanel : null; |
| 2517 | + const target = buildHash(tab, currentSub); |
| 2518 | + if (window.location.hash !== target) { |
| 2519 | + window.history.replaceState(null, '', target); |
| 2520 | + } |
| 2521 | + // run once on mount |
| 2522 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| 2523 | + }, []); |
| 2524 | + |
| 2525 | + // Sync state when user navigates with Back/Forward or edits the URL |
| 2526 | + React.useEffect(() => { |
| 2527 | + const sync = () => { |
| 2528 | + const p = parseHash(); |
| 2529 | + setTab(p.tab); |
| 2530 | + if (p.tab === 'feeds' && p.sub) setFeedSub(p.sub); |
| 2531 | + if (p.tab === 'checker' && p.sub) setCheckerSub(p.sub); |
| 2532 | + if (p.tab === 'guide' && p.sub) setGuidePanel(p.sub); |
| 2533 | + }; |
| 2534 | + window.addEventListener('hashchange', sync); |
| 2535 | + window.addEventListener('popstate', sync); |
| 2536 | + return () => { |
| 2537 | + window.removeEventListener('hashchange', sync); |
| 2538 | + window.removeEventListener('popstate', sync); |
| 2539 | + }; |
| 2540 | + }, []); |
| 2541 | + |
| 2542 | + const selectTab = (id) => { |
| 2543 | + setTab(id); |
| 2544 | + const sub = |
| 2545 | + id === 'feeds' ? feedSub : |
| 2546 | + id === 'checker' ? checkerSub : |
| 2547 | + id === 'guide' ? guidePanel : null; |
| 2548 | + pushHash(id, sub); |
| 2549 | + }; |
| 2550 | + const selectFeedSub = (id) => { setFeedSub(id); pushHash('feeds', id); }; |
| 2551 | + const selectCheckerSub = (id) => { setCheckerSub(id); pushHash('checker', id); }; |
| 2552 | + const selectGuidePanel = (id) => { setGuidePanel(id); pushHash('guide', id); }; |
| 2553 | + |
2457 | 2554 | const tabs = [{ |
2458 | 2555 | id: 'dashboard', |
2459 | 2556 | l: 'Dashboard' |
|
2566 | 2663 | } |
2567 | 2664 | }, tabs.map(t => /*#__PURE__*/React.createElement("button", { |
2568 | 2665 | key: t.id, |
2569 | | - onClick: () => setTab(t.id), |
| 2666 | + onClick: () => selectTab(t.id), |
2570 | 2667 | style: { |
2571 | 2668 | background: tab === t.id ? '#161b22' : 'transparent', |
2572 | 2669 | border: tab === t.id ? '1px solid #30363d' : '1px solid transparent', |
|
2873 | 2970 | } |
2874 | 2971 | }, feedSubs.map(s => /*#__PURE__*/React.createElement("button", { |
2875 | 2972 | key: s.id, |
2876 | | - onClick: () => setFeedSub(s.id), |
| 2973 | + onClick: () => selectFeedSub(s.id), |
2877 | 2974 | style: { |
2878 | 2975 | display: 'flex', |
2879 | 2976 | alignItems: 'center', |
|
3058 | 3155 | cursor: 'pointer', |
3059 | 3156 | textDecoration: 'none' |
3060 | 3157 | } |
3061 | | - }, "Sensitive"))))))), tab === 'checker' && /*#__PURE__*/React.createElement(ExtensionChecker, null), tab === 'policy' && /*#__PURE__*/React.createElement(PolicyGenerator, null), tab === 'guide' && /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", { |
| 3158 | + }, "Sensitive"))))))), tab === 'checker' && /*#__PURE__*/React.createElement(ExtensionChecker, { subTab: checkerSub, setSubTab: selectCheckerSub }), tab === 'policy' && /*#__PURE__*/React.createElement(PolicyGenerator, null), tab === 'guide' && /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", { |
3062 | 3159 | style: { |
3063 | 3160 | marginBottom: 20 |
3064 | 3161 | } |
|
3082 | 3179 | } |
3083 | 3180 | }, gps.map(p => /*#__PURE__*/React.createElement("button", { |
3084 | 3181 | key: p.id, |
3085 | | - onClick: () => !p.soon && setGuidePanel(p.id), |
| 3182 | + onClick: () => !p.soon && selectGuidePanel(p.id), |
3086 | 3183 | style: { |
3087 | 3184 | background: guidePanel === p.id ? '#161b22' : 'transparent', |
3088 | 3185 | border: guidePanel === p.id ? '1px solid #58a6ff33' : '1px solid #1b1f27', |
|
0 commit comments