- ci: make frontend CI read-only with strict diff check and deterministic calls.json - ci: pin external GitHub Actions in workflows to immutable commit SHAs - ci: add openwrt-e2e blocking gate job running docker_e2e_test.sh - fix(diagnostic): fix contstants.ts typo and update all 13 import sites - fix(acl): remove unused /bin/busybox exec from rpcd ACL and add regression test - feat(updates): add low-space update fallback when creating sing-box.old is impossible - fix(routes): preserve dedicated Discord gateway subnets (162.159.128.0/21) - feat(ui): implement responsive auto-fit grid and mobile modal viewport bounds - test: add viewport regression test suite in vitest
2059 lines
65 KiB
TypeScript
2059 lines
65 KiB
TypeScript
import { TachyonShellMethods } from '../../../methods';
|
||
import { renderButton } from '../../../../partials';
|
||
import { showToast } from '../../../../helpers/showToast';
|
||
import { Tachyon } from '../../../types';
|
||
|
||
export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
|
||
let pollingInterval: ReturnType<typeof setInterval> | null = null;
|
||
let isPolling = false;
|
||
|
||
// Modal State
|
||
let activeTab: 'benchmark' | 'patterns' | 'custom' | 'ai' | 'history' =
|
||
'benchmark';
|
||
let selectedEngine: Tachyon.FuzzerEngine = 'zapret2';
|
||
let selectedTarget: Tachyon.FuzzerTarget = 'youtube_suite';
|
||
let customUrl = '';
|
||
let selectedRuleSection = '';
|
||
let selectedMode: Tachyon.FuzzerMode = 'presets';
|
||
let isRunning = false;
|
||
let currentState: Tachyon.FuzzerState | null = null;
|
||
let resultFilter: 'all' | 'success' | 'fast' = 'all';
|
||
let autoApplyEnabled = false;
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
let currentDpiDetection: Tachyon.FuzzerDpiDetection | null = null;
|
||
|
||
// Patterns Config State
|
||
let patternsConfig: Tachyon.FuzzerPatternsConfig = {
|
||
zapret2: {
|
||
splits: ['1', '2', '3', 'midsld', 'sniext+4', '1,midsld', '1,sniext+2'],
|
||
foolings: ['badseq', 'md5sig', 'badack', 'datanoack', 'fakeddrop'],
|
||
ttls: [2, 3, 4, 5, 6, 8],
|
||
seqovls: ['1', '2'],
|
||
wsizes: ['1'],
|
||
payloads: ['tls_client_hello', 'http_req', 'quic_initial'],
|
||
},
|
||
zapret: {
|
||
splits: ['1', '2', 'midsld', 'sniext+4', '1,midsld'],
|
||
foolings: ['badseq', 'md5sig', 'badack', 'datanoack'],
|
||
ttls: [2, 3, 4, 6, 8],
|
||
split_modes: ['split2', 'disorder2', 'fake,split2', 'fake,disorder2'],
|
||
},
|
||
byedpi: {
|
||
splits: ['1', '2', '1+sniext', 'midsld'],
|
||
disorders: ['1', '2'],
|
||
ttls: [2, 3, 4, 6, 8],
|
||
oobs: ['1', '2'],
|
||
autos: ['t,r,a,s', 'r,s', 't,a'],
|
||
tlsrecs: ['1+sniext'],
|
||
ipfrags: ['24'],
|
||
},
|
||
custom_strategies: [],
|
||
};
|
||
|
||
const modalContainer = E('div', {
|
||
class: 'tachyon_fuzzer_modal',
|
||
style:
|
||
'display: flex; flex-direction: column; gap: 14px; width: 100%; box-sizing: border-box;',
|
||
});
|
||
|
||
// ── Tab Bar Navigation ────────────────────────────────────────────────────
|
||
const tabButtons: Record<string, HTMLElement> = {};
|
||
|
||
const createTabButton = (
|
||
id: 'benchmark' | 'patterns' | 'custom' | 'ai' | 'history',
|
||
icon: string,
|
||
label: string,
|
||
) => {
|
||
const btn = E(
|
||
'button',
|
||
{
|
||
type: 'button',
|
||
class: `cbi-button ${activeTab === id ? 'cbi-button-action' : 'cbi-button-neutral'}`,
|
||
style:
|
||
'display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; font-size: 12px; font-weight: 500; border-radius: 6px; cursor: pointer;',
|
||
},
|
||
[E('span', {}, icon), E('span', {}, label)],
|
||
);
|
||
btn.addEventListener('click', () => {
|
||
activeTab = id;
|
||
updateTabVisibility();
|
||
});
|
||
tabButtons[id] = btn;
|
||
return btn;
|
||
};
|
||
|
||
const tabBar = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; gap: 8px; border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.12)); padding-bottom: 10px; flex-wrap: wrap;',
|
||
},
|
||
[
|
||
createTabButton('benchmark', '⚡', _('Benchmark & Test')),
|
||
createTabButton('patterns', '🛠️', _('Pattern Builder')),
|
||
createTabButton('custom', '➕', _('My Strategies')),
|
||
createTabButton('ai', '🧠', _('AI Doctor RAG')),
|
||
createTabButton('history', '📋', _('History')),
|
||
],
|
||
);
|
||
|
||
const tabContentBenchmark = E('div', {
|
||
style: 'display: flex; flex-direction: column; gap: 14px;',
|
||
});
|
||
const tabContentPatterns = E('div', {
|
||
style: 'display: none; flex-direction: column; gap: 14px;',
|
||
});
|
||
const tabContentCustom = E('div', {
|
||
style: 'display: none; flex-direction: column; gap: 14px;',
|
||
});
|
||
const tabContentAi = E('div', {
|
||
style: 'display: none; flex-direction: column; gap: 14px;',
|
||
});
|
||
const tabContentHistory = E('div', {
|
||
style: 'display: none; flex-direction: column; gap: 14px;',
|
||
});
|
||
|
||
const updateTabVisibility = () => {
|
||
for (const key in tabButtons) {
|
||
tabButtons[key].className =
|
||
`cbi-button ${activeTab === key ? 'cbi-button-action' : 'cbi-button-neutral'}`;
|
||
}
|
||
tabContentBenchmark.style.display =
|
||
activeTab === 'benchmark' ? 'flex' : 'none';
|
||
tabContentPatterns.style.display =
|
||
activeTab === 'patterns' ? 'flex' : 'none';
|
||
tabContentCustom.style.display = activeTab === 'custom' ? 'flex' : 'none';
|
||
tabContentAi.style.display = activeTab === 'ai' ? 'flex' : 'none';
|
||
tabContentHistory.style.display = activeTab === 'history' ? 'flex' : 'none';
|
||
|
||
if (activeTab === 'patterns') renderPatternsTab();
|
||
if (activeTab === 'custom') renderCustomTab();
|
||
if (activeTab === 'history') renderHistoryTab();
|
||
};
|
||
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
// TAB 1: BENCHMARK & RUN
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
|
||
const controlsGrid = E('div', {
|
||
style:
|
||
'display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); gap: 10px; align-items: end;',
|
||
});
|
||
|
||
// 1. Engine Select
|
||
const engineSelect = E(
|
||
'select',
|
||
{ class: 'cbi-input-select', style: 'width: 100%;' },
|
||
[
|
||
E('option', { value: 'zapret2' }, _('Zapret v2 (nfqws2)')),
|
||
E('option', { value: 'zapret' }, _('Zapret v1 (nfqws)')),
|
||
E('option', { value: 'byedpi' }, _('ByeDPI (ciadpi)')),
|
||
E('option', { value: 'all' }, _('All Engines (Full Matrix)')),
|
||
],
|
||
);
|
||
engineSelect.addEventListener('change', () => {
|
||
selectedEngine = (engineSelect as HTMLSelectElement)
|
||
.value as Tachyon.FuzzerEngine;
|
||
});
|
||
|
||
const engineGroup = E(
|
||
'div',
|
||
{ style: 'display: flex; flex-direction: column; gap: 4px;' },
|
||
[
|
||
E(
|
||
'label',
|
||
{ style: 'font-size: 11px; font-weight: 600; opacity: 0.85;' },
|
||
_('DPI Engine'),
|
||
),
|
||
engineSelect,
|
||
],
|
||
);
|
||
|
||
// 2. Target Preset Select
|
||
const targetSelect = E(
|
||
'select',
|
||
{ class: 'cbi-input-select', style: 'width: 100%;' },
|
||
[
|
||
E(
|
||
'option',
|
||
{ value: 'youtube_suite', selected: true },
|
||
_('🎬 YouTube Full Suite (Web + 4K Stream + CDN)'),
|
||
),
|
||
E(
|
||
'option',
|
||
{ value: 'discord_suite' },
|
||
_('💬 Discord Suite (Gateway + Voice + CDN)'),
|
||
),
|
||
E(
|
||
'option',
|
||
{ value: 'twitch_suite' },
|
||
_('🟣 Twitch Suite (Live Stream + HLS CDN)'),
|
||
),
|
||
E(
|
||
'option',
|
||
{ value: 'twitter_suite' },
|
||
_('🐦 X / Twitter Suite (API + Media CDN)'),
|
||
),
|
||
E('option', { value: 'chatgpt_suite' }, _('🤖 ChatGPT / OpenAI Suite')),
|
||
E(
|
||
'option',
|
||
{ value: 'instagram_suite' },
|
||
_('📸 Instagram / Meta (Web + CDN)'),
|
||
),
|
||
E(
|
||
'option',
|
||
{ value: 'telegram_suite' },
|
||
_('✈️ Telegram Suite (Web + Bot API)'),
|
||
),
|
||
E('option', { value: 'rutracker_suite' }, _('🏴☠️ RuTracker Suite')),
|
||
E('option', { value: 'quic_http3' }, _('⚡ QUIC / HTTP/3 (UDP 443)')),
|
||
E('option', { value: 'custom' }, _('🌐 Custom Target URL...')),
|
||
],
|
||
);
|
||
|
||
const customUrlInput = E('input', {
|
||
type: 'text',
|
||
class: 'cbi-input-text',
|
||
placeholder: 'https://example.com',
|
||
style: 'width: 100%; display: none; margin-top: 4px;',
|
||
});
|
||
|
||
targetSelect.addEventListener('change', () => {
|
||
selectedTarget = (targetSelect as HTMLSelectElement)
|
||
.value as Tachyon.FuzzerTarget;
|
||
(customUrlInput as HTMLElement).style.display =
|
||
selectedTarget === 'custom' ? 'block' : 'none';
|
||
});
|
||
|
||
customUrlInput.addEventListener('input', () => {
|
||
customUrl = (customUrlInput as HTMLInputElement).value.trim();
|
||
});
|
||
|
||
const targetGroup = E(
|
||
'div',
|
||
{ style: 'display: flex; flex-direction: column; gap: 4px;' },
|
||
[
|
||
E(
|
||
'label',
|
||
{ style: 'font-size: 11px; font-weight: 600; opacity: 0.85;' },
|
||
_('Target Service / Suite'),
|
||
),
|
||
targetSelect,
|
||
customUrlInput,
|
||
],
|
||
);
|
||
|
||
// 3. Search Mode Select
|
||
const modeSelect = E(
|
||
'select',
|
||
{ class: 'cbi-input-select', style: 'width: 100%;' },
|
||
[
|
||
E(
|
||
'option',
|
||
{ value: 'presets', selected: true },
|
||
_('⚡ Quick Benchmark (Presets ~30+)'),
|
||
),
|
||
E(
|
||
'option',
|
||
{ value: 'combinatorial' },
|
||
_('🔍 Combinatorial Deep Fuzzing (~150-300+)'),
|
||
),
|
||
E('option', { value: 'custom' }, _('🛠️ My Custom Strategies Only')),
|
||
],
|
||
);
|
||
modeSelect.addEventListener('change', () => {
|
||
selectedMode = (modeSelect as HTMLSelectElement)
|
||
.value as Tachyon.FuzzerMode;
|
||
});
|
||
|
||
const modeGroup = E(
|
||
'div',
|
||
{ style: 'display: flex; flex-direction: column; gap: 4px;' },
|
||
[
|
||
E(
|
||
'label',
|
||
{ style: 'font-size: 11px; font-weight: 600; opacity: 0.85;' },
|
||
_('Search Mode'),
|
||
),
|
||
modeSelect,
|
||
],
|
||
);
|
||
|
||
// 4. Rule Apply Target Select
|
||
const ruleSelect = E(
|
||
'select',
|
||
{ class: 'cbi-input-select', style: 'width: 100%;' },
|
||
[
|
||
E('option', { value: '', selected: true }, _('Provider Global Default')),
|
||
...ruleNames.map((r) => E('option', { value: r }, `${_('Rule:')} ${r}`)),
|
||
],
|
||
);
|
||
ruleSelect.addEventListener('change', () => {
|
||
selectedRuleSection = (ruleSelect as HTMLSelectElement).value;
|
||
});
|
||
|
||
const ruleGroup = E(
|
||
'div',
|
||
{ style: 'display: flex; flex-direction: column; gap: 4px;' },
|
||
[
|
||
E(
|
||
'label',
|
||
{ style: 'font-size: 11px; font-weight: 600; opacity: 0.85;' },
|
||
_('Apply Strategy To'),
|
||
),
|
||
ruleSelect,
|
||
],
|
||
);
|
||
controlsGrid.append(engineGroup, targetGroup, modeGroup, ruleGroup);
|
||
|
||
// Auto-Apply Toggle
|
||
const autoApplyCheckbox = E('input', {
|
||
type: 'checkbox',
|
||
id: 'tachyon-fuzzer-auto-apply',
|
||
style: 'margin: 0;',
|
||
});
|
||
autoApplyCheckbox.addEventListener('change', () => {
|
||
autoApplyEnabled = (autoApplyCheckbox as HTMLInputElement).checked;
|
||
});
|
||
|
||
const autoApplyGroup = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; align-items: end; padding: 4px 0; grid-column: 1 / -1;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'font-size: 11px; cursor: pointer; display: flex; align-items: center; gap: 4px;',
|
||
},
|
||
[autoApplyCheckbox, E('span', {}, _('Auto-apply best strategy'))],
|
||
),
|
||
],
|
||
);
|
||
controlsGrid.append(autoApplyGroup);
|
||
|
||
// DPI Detection Banner
|
||
const dpiDetectionBanner = E('div', {
|
||
id: 'tachyon-fuzzer-dpi-banner',
|
||
style:
|
||
'display: none; padding: 10px 14px; border-radius: 6px; font-size: 12px; line-height: 1.4;',
|
||
});
|
||
|
||
// Progress Bar
|
||
const progressContainer = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: none; flex-direction: column; gap: 6px; padding: 10px 14px; background: var(--background-color-secondary, rgba(0,0,0,0.18)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.08));',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; justify-content: space-between; font-size: 12px; font-weight: 600;',
|
||
},
|
||
[
|
||
E(
|
||
'span',
|
||
{ id: 'tachyon-fuzzer-status-text' },
|
||
_('Benchmarking in progress...'),
|
||
),
|
||
E('span', { id: 'tachyon-fuzzer-progress-pct' }, '0%'),
|
||
],
|
||
),
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden;',
|
||
},
|
||
[
|
||
E('div', {
|
||
id: 'tachyon-fuzzer-progress-bar',
|
||
style:
|
||
'height: 100%; width: 0%; background: #007bff; transition: width 0.3s ease;',
|
||
}),
|
||
],
|
||
),
|
||
E(
|
||
'div',
|
||
{
|
||
id: 'tachyon-fuzzer-current-strategy',
|
||
style: 'font-size: 11px; opacity: 0.75; font-family: monospace;',
|
||
},
|
||
'',
|
||
),
|
||
],
|
||
);
|
||
|
||
const applyBestBtn = renderButton({
|
||
text: _('🏆 Apply Best Match'),
|
||
classNames: ['cbi-button-save'],
|
||
disabled: true,
|
||
onClick: () => handleApplyBest(),
|
||
});
|
||
|
||
// Results Filter Toolbar
|
||
const filterToolbar = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px;',
|
||
},
|
||
[
|
||
E('div', { style: 'display: flex; gap: 6px; align-items: center;' }, [
|
||
E(
|
||
'span',
|
||
{ style: 'font-size: 11px; opacity: 0.7; margin-right: 4px;' },
|
||
_('Filter:'),
|
||
),
|
||
(() => {
|
||
const bAll = E(
|
||
'button',
|
||
{
|
||
type: 'button',
|
||
class: 'cbi-button cbi-button-action',
|
||
style: 'padding: 2px 10px; font-size: 11px;',
|
||
},
|
||
_('All'),
|
||
);
|
||
const bOk = E(
|
||
'button',
|
||
{
|
||
type: 'button',
|
||
class: 'cbi-button cbi-button-neutral',
|
||
style: 'padding: 2px 10px; font-size: 11px;',
|
||
},
|
||
_('✅ Working only'),
|
||
);
|
||
const bFast = E(
|
||
'button',
|
||
{
|
||
type: 'button',
|
||
class: 'cbi-button cbi-button-neutral',
|
||
style: 'padding: 2px 10px; font-size: 11px;',
|
||
},
|
||
_('⚡ Fast (>1MB/s)'),
|
||
);
|
||
|
||
const setFilter = (
|
||
f: 'all' | 'success' | 'fast',
|
||
btn: HTMLElement,
|
||
) => {
|
||
resultFilter = f;
|
||
[bAll, bOk, bFast].forEach(
|
||
(b) => (b.className = 'cbi-button cbi-button-neutral'),
|
||
);
|
||
btn.className = 'cbi-button cbi-button-action';
|
||
if (currentState) renderResults(currentState);
|
||
};
|
||
|
||
bAll.addEventListener('click', () => setFilter('all', bAll));
|
||
bOk.addEventListener('click', () => setFilter('success', bOk));
|
||
bFast.addEventListener('click', () => setFilter('fast', bFast));
|
||
|
||
return E('div', { style: 'display: flex; gap: 4px;' }, [
|
||
bAll,
|
||
bOk,
|
||
bFast,
|
||
]);
|
||
})(),
|
||
]),
|
||
E('div', { style: 'display: flex; gap: 8px;' }, [applyBestBtn]),
|
||
],
|
||
);
|
||
|
||
// Results Table
|
||
const resultsContainer = E('div', {
|
||
style:
|
||
'max-height: 380px; overflow-y: auto; overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; width: 100%; box-sizing: border-box;',
|
||
});
|
||
|
||
const tableEl = E(
|
||
'table',
|
||
{
|
||
class: 'table cbi-section-table',
|
||
style:
|
||
'width: 100%; min-width: 620px; margin: 0; font-size: 12px; text-align: left; border-collapse: collapse;',
|
||
},
|
||
[
|
||
E(
|
||
'thead',
|
||
{
|
||
class: 'cbi-section-table-titles',
|
||
style:
|
||
'background: var(--background-color-secondary, rgba(0,0,0,0.25)); position: sticky; top: 0; z-index: 2;',
|
||
},
|
||
[
|
||
E('tr', { class: 'cbi-section-table-titles' }, [
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 35px;',
|
||
},
|
||
'#',
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 75px;',
|
||
},
|
||
_('Engine'),
|
||
),
|
||
E(
|
||
'th',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px 10px;' },
|
||
_('Strategy & Parameters'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 95px;',
|
||
},
|
||
_('Status / Suite'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 65px;',
|
||
},
|
||
_('TTFB'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 75px;',
|
||
},
|
||
_('Speed'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 80px;',
|
||
},
|
||
_('Score'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; width: 85px; text-align: center;',
|
||
},
|
||
_('Actions'),
|
||
),
|
||
]),
|
||
],
|
||
),
|
||
E('tbody', { id: 'tachyon-fuzzer-results-tbody' }, [
|
||
E(
|
||
'tr',
|
||
{},
|
||
E(
|
||
'td',
|
||
{
|
||
colSpan: 8,
|
||
style: 'padding: 24px; text-align: center; opacity: 0.6;',
|
||
},
|
||
_(
|
||
'No benchmark results yet. Configure options and click "Start Benchmark".',
|
||
),
|
||
),
|
||
),
|
||
]),
|
||
],
|
||
);
|
||
|
||
resultsContainer.appendChild(tableEl);
|
||
|
||
// Footer Actions for Benchmark
|
||
const startBtn = renderButton({
|
||
text: _('🚀 Start Benchmark'),
|
||
classNames: ['cbi-button-action'],
|
||
onClick: () => handleToggleRun(),
|
||
});
|
||
|
||
const footerActions = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; justify-content: space-between; align-items: center; padding-top: 6px;',
|
||
},
|
||
[
|
||
startBtn,
|
||
renderButton({
|
||
text: _('Close'),
|
||
classNames: ['cbi-button-neutral'],
|
||
onClick: () => {
|
||
stopPolling();
|
||
ui.hideModal();
|
||
},
|
||
}),
|
||
],
|
||
);
|
||
|
||
tabContentBenchmark.append(
|
||
controlsGrid,
|
||
dpiDetectionBanner,
|
||
progressContainer,
|
||
filterToolbar,
|
||
resultsContainer,
|
||
footerActions,
|
||
);
|
||
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
// TAB 2: PATTERN BUILDER & MATRIX CONFIG
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
|
||
const renderChipGroup = (
|
||
title: string,
|
||
items: (string | number)[],
|
||
onAdd: (val: string) => void,
|
||
onRemove: (idx: number) => void,
|
||
) => {
|
||
const input = E('input', {
|
||
type: 'text',
|
||
class: 'cbi-input-text',
|
||
placeholder: _('+ add'),
|
||
style: 'width: 80px; padding: 2px 6px; font-size: 11px;',
|
||
});
|
||
|
||
input.addEventListener('keydown', (e: KeyboardEvent) => {
|
||
if (e.key === 'Enter') {
|
||
const v = (input as HTMLInputElement).value.trim();
|
||
if (v) {
|
||
onAdd(v);
|
||
(input as HTMLInputElement).value = '';
|
||
renderPatternsTab();
|
||
}
|
||
}
|
||
});
|
||
|
||
const chips = items.map((item, idx) => {
|
||
const chip = E(
|
||
'span',
|
||
{
|
||
style:
|
||
'display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--background-color-secondary, rgba(255,255,255,0.08)); border: 1px solid var(--border-color, rgba(255,255,255,0.12)); border-radius: 4px; font-size: 11px; font-family: monospace;',
|
||
},
|
||
[
|
||
E('span', {}, String(item)),
|
||
(() => {
|
||
const del = E(
|
||
'span',
|
||
{
|
||
style:
|
||
'cursor: pointer; opacity: 0.6; font-size: 12px; margin-left: 2px;',
|
||
},
|
||
'×',
|
||
);
|
||
del.addEventListener('click', () => {
|
||
onRemove(idx);
|
||
renderPatternsTab();
|
||
});
|
||
return del;
|
||
})(),
|
||
],
|
||
);
|
||
return chip;
|
||
});
|
||
|
||
return E(
|
||
'div',
|
||
{ style: 'display: flex; flex-direction: column; gap: 4px;' },
|
||
[
|
||
E(
|
||
'span',
|
||
{ style: 'font-size: 11px; font-weight: 600; opacity: 0.8;' },
|
||
title,
|
||
),
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; flex-wrap: wrap; gap: 6px; align-items: center;',
|
||
},
|
||
[...chips, input],
|
||
),
|
||
],
|
||
);
|
||
};
|
||
|
||
const renderPatternsTab = () => {
|
||
tabContentPatterns.replaceChildren();
|
||
|
||
const z2 = patternsConfig.zapret2;
|
||
const z1 = patternsConfig.zapret;
|
||
const bd = patternsConfig.byedpi;
|
||
|
||
const z2Card = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 13px; font-weight: bold;' },
|
||
'⚡ Zapret v2 (nfqws2) Matrix Patterns',
|
||
),
|
||
renderChipGroup(
|
||
_('Split Positions (pos)'),
|
||
z2.splits,
|
||
(v) => z2.splits.push(v),
|
||
(i) => z2.splits.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('Fooling Methods (fooling)'),
|
||
z2.foolings,
|
||
(v) => z2.foolings.push(v),
|
||
(i) => z2.foolings.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('TTL Values (ttl)'),
|
||
z2.ttls,
|
||
(v) => z2.ttls.push(Number(v) || 4),
|
||
(i) => z2.ttls.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('Sequence Overlaps (seqovl)'),
|
||
z2.seqovls,
|
||
(v) => z2.seqovls.push(v),
|
||
(i) => z2.seqovls.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('TCP Window Sizes (wsize)'),
|
||
z2.wsizes,
|
||
(v) => z2.wsizes.push(v),
|
||
(i) => z2.wsizes.splice(i, 1),
|
||
),
|
||
],
|
||
);
|
||
|
||
const z1Card = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 13px; font-weight: bold;' },
|
||
'⚡ Zapret v1 (nfqws) Matrix Patterns',
|
||
),
|
||
renderChipGroup(
|
||
_('Split Modes (dpi-desync)'),
|
||
z1.split_modes,
|
||
(v) => z1.split_modes.push(v),
|
||
(i) => z1.split_modes.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('Split Positions (dpi-desync-split-pos)'),
|
||
z1.splits,
|
||
(v) => z1.splits.push(v),
|
||
(i) => z1.splits.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('Fooling Methods (dpi-desync-fooling)'),
|
||
z1.foolings,
|
||
(v) => z1.foolings.push(v),
|
||
(i) => z1.foolings.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('TTL Values (dpi-desync-ttl)'),
|
||
z1.ttls,
|
||
(v) => z1.ttls.push(Number(v) || 4),
|
||
(i) => z1.ttls.splice(i, 1),
|
||
),
|
||
],
|
||
);
|
||
|
||
const bdCard = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 13px; font-weight: bold;' },
|
||
'⚡ ByeDPI (ciadpi) Matrix Patterns',
|
||
),
|
||
renderChipGroup(
|
||
_('Split Values (-s)'),
|
||
bd.splits,
|
||
(v) => bd.splits.push(v),
|
||
(i) => bd.splits.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('Disorder Values (-d)'),
|
||
bd.disorders,
|
||
(v) => bd.disorders.push(v),
|
||
(i) => bd.disorders.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('OOB Values (-o)'),
|
||
bd.oobs,
|
||
(v) => bd.oobs.push(v),
|
||
(i) => bd.oobs.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('Auto Modes (--auto)'),
|
||
bd.autos,
|
||
(v) => bd.autos.push(v),
|
||
(i) => bd.autos.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('TLS-Rec Bounds (--tlsrec)'),
|
||
bd.tlsrecs,
|
||
(v) => bd.tlsrecs.push(v),
|
||
(i) => bd.tlsrecs.splice(i, 1),
|
||
),
|
||
renderChipGroup(
|
||
_('IP Frag Bounds (--ip-frag)'),
|
||
bd.ipfrags,
|
||
(v) => bd.ipfrags.push(v),
|
||
(i) => bd.ipfrags.splice(i, 1),
|
||
),
|
||
],
|
||
);
|
||
|
||
const patternsFooter = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; justify-content: space-between; align-items: center; padding-top: 6px;',
|
||
},
|
||
[
|
||
renderButton({
|
||
text: _('💾 Save Custom Patterns'),
|
||
classNames: ['cbi-button-save'],
|
||
onClick: async () => {
|
||
const res =
|
||
await TachyonShellMethods.saveFuzzerPatterns(patternsConfig);
|
||
if (res.success) {
|
||
showToast(_('Patterns saved successfully!'), 'success');
|
||
} else {
|
||
showToast(_('Failed to save patterns'), 'error');
|
||
}
|
||
},
|
||
}),
|
||
renderButton({
|
||
text: _('🔄 Reset to Factory Defaults'),
|
||
classNames: ['cbi-button-neutral'],
|
||
onClick: async () => {
|
||
const res = await TachyonShellMethods.resetFuzzerPatterns();
|
||
if (res.success && res.data?.patterns) {
|
||
patternsConfig = res.data.patterns;
|
||
renderPatternsTab();
|
||
showToast(_('Patterns reset to defaults!'), 'success');
|
||
}
|
||
},
|
||
}),
|
||
],
|
||
);
|
||
|
||
tabContentPatterns.append(z2Card, z1Card, bdCard, patternsFooter);
|
||
};
|
||
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
// TAB 3: CUSTOM STRATEGIES
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
|
||
const renderCustomTab = () => {
|
||
tabContentCustom.replaceChildren();
|
||
|
||
const nameInput = E('input', {
|
||
type: 'text',
|
||
class: 'cbi-input-text',
|
||
placeholder: _('Strategy Name'),
|
||
style: 'flex: 1 1 180px; min-width: 140px;',
|
||
});
|
||
const engineSel = E(
|
||
'select',
|
||
{
|
||
class: 'cbi-input-select',
|
||
style: 'flex: 0 1 140px; min-width: 120px;',
|
||
},
|
||
[
|
||
E('option', { value: 'zapret2' }, _('Zapret v2')),
|
||
E('option', { value: 'zapret' }, _('Zapret v1')),
|
||
E('option', { value: 'byedpi' }, _('ByeDPI')),
|
||
],
|
||
);
|
||
const argsInput = E('input', {
|
||
type: 'text',
|
||
class: 'cbi-input-text',
|
||
placeholder: _('Command-line arguments string...'),
|
||
style: 'flex: 2 1 240px; min-width: 180px; font-family: monospace;',
|
||
});
|
||
|
||
const addBtn = renderButton({
|
||
text: _('+ Add Strategy'),
|
||
classNames: ['cbi-button-action'],
|
||
onClick: async () => {
|
||
const name = (nameInput as HTMLInputElement).value.trim();
|
||
const eng = (engineSel as HTMLSelectElement)
|
||
.value as Tachyon.FuzzerEngine;
|
||
const args = (argsInput as HTMLInputElement).value.trim();
|
||
if (!name || !args) {
|
||
showToast(_('Please specify strategy name and arguments'), 'error');
|
||
return;
|
||
}
|
||
|
||
patternsConfig.custom_strategies =
|
||
patternsConfig.custom_strategies || [];
|
||
patternsConfig.custom_strategies.push({
|
||
id: `custom_${Date.now()}`,
|
||
name,
|
||
engine: eng as 'zapret2' | 'zapret' | 'byedpi',
|
||
args,
|
||
description: _('User custom strategy'),
|
||
});
|
||
|
||
await TachyonShellMethods.saveFuzzerPatterns(patternsConfig);
|
||
showToast(_('Custom strategy added!'), 'success');
|
||
(nameInput as HTMLInputElement).value = '';
|
||
(argsInput as HTMLInputElement).value = '';
|
||
renderCustomTab();
|
||
},
|
||
});
|
||
|
||
const addCard = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 8px;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 12px; font-weight: 600;' },
|
||
_('Add Custom DPI Strategy'),
|
||
),
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; gap: 8px; flex-wrap: wrap; align-items: center;',
|
||
},
|
||
[nameInput, engineSel, argsInput, addBtn],
|
||
),
|
||
],
|
||
);
|
||
|
||
const customList = patternsConfig.custom_strategies || [];
|
||
const listCard = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;',
|
||
},
|
||
[
|
||
E(
|
||
'table',
|
||
{ class: 'table', style: 'width: 100%; margin: 0; font-size: 12px;' },
|
||
[
|
||
E('thead', {}, [
|
||
E('tr', {}, [
|
||
E('th', { style: 'padding: 8px;' }, _('Name')),
|
||
E('th', { style: 'padding: 8px; width: 90px;' }, _('Engine')),
|
||
E('th', { style: 'padding: 8px;' }, _('Arguments')),
|
||
E(
|
||
'th',
|
||
{
|
||
style: 'padding: 8px; width: 80px; text-align: center;',
|
||
},
|
||
_('Actions'),
|
||
),
|
||
]),
|
||
]),
|
||
E(
|
||
'tbody',
|
||
{},
|
||
customList.length === 0
|
||
? [
|
||
E(
|
||
'tr',
|
||
{},
|
||
E(
|
||
'td',
|
||
{
|
||
colSpan: 4,
|
||
style:
|
||
'padding: 20px; text-align: center; opacity: 0.6;',
|
||
},
|
||
_('No custom strategies added yet.'),
|
||
),
|
||
),
|
||
]
|
||
: customList.map((cs, idx) => {
|
||
return E('tr', {}, [
|
||
E(
|
||
'td',
|
||
{ style: 'padding: 8px; font-weight: 600;' },
|
||
cs.name,
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
style:
|
||
'padding: 8px; font-family: monospace; font-size: 11px;',
|
||
},
|
||
cs.engine,
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
style:
|
||
'padding: 8px; font-family: monospace; font-size: 11px; word-break: break-all;',
|
||
},
|
||
cs.args,
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
style: 'padding: 8px; text-align: center;',
|
||
},
|
||
[
|
||
(() => {
|
||
const delBtn = E(
|
||
'button',
|
||
{
|
||
type: 'button',
|
||
class: 'cbi-button cbi-button-reset',
|
||
style: 'padding: 2px 8px; font-size: 11px;',
|
||
},
|
||
_('Delete'),
|
||
);
|
||
delBtn.addEventListener('click', async () => {
|
||
patternsConfig.custom_strategies.splice(idx, 1);
|
||
await TachyonShellMethods.saveFuzzerPatterns(
|
||
patternsConfig,
|
||
);
|
||
showToast(_('Strategy deleted'), 'success');
|
||
renderCustomTab();
|
||
});
|
||
return delBtn;
|
||
})(),
|
||
],
|
||
),
|
||
]);
|
||
}),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
|
||
tabContentCustom.append(addCard, listCard);
|
||
};
|
||
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
// TAB 4: AI DOCTOR RAG
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
|
||
const aiPromptInput = E('input', {
|
||
type: 'text',
|
||
class: 'cbi-input-text',
|
||
placeholder: _(
|
||
'Optional context (e.g. "Rostelecom, YouTube 4K buffering is slow")...',
|
||
),
|
||
style: 'flex: 1 1 240px; min-width: 180px; font-size: 12px;',
|
||
});
|
||
|
||
const aiSynthesizeBtn = renderButton({
|
||
text: _('🧠 Synthesize with AI'),
|
||
classNames: ['cbi-button-action'],
|
||
onClick: () => handleAiSynthesize(),
|
||
});
|
||
|
||
const aiAnalysisContainer = E(
|
||
'div',
|
||
{
|
||
id: 'tachyon-fuzzer-ai-analysis',
|
||
style:
|
||
'display: none; padding: 12px; background: rgba(0, 123, 255, 0.08); border-left: 3px solid #007bff; border-radius: 4px; font-size: 12px; line-height: 1.4;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'font-weight: bold; margin-bottom: 6px; display: flex; align-items: center; gap: 6px;',
|
||
},
|
||
[
|
||
E('span', {}, '🧠'),
|
||
E('span', {}, _('AI Diagnostics & Strategy Rationale')),
|
||
],
|
||
),
|
||
E(
|
||
'div',
|
||
{ id: 'tachyon-fuzzer-ai-analysis-text', style: 'opacity: 0.9;' },
|
||
'',
|
||
),
|
||
],
|
||
);
|
||
|
||
const aiSynthesizerCard = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 14px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 12px;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'font-size: 13px; font-weight: bold; display: flex; align-items: center; justify-content: space-between;',
|
||
},
|
||
[
|
||
E('span', {}, '🧠 ' + _('AI DPI Engineer & Strategy Synthesizer')),
|
||
E(
|
||
'span',
|
||
{ style: 'font-size: 11px; opacity: 0.6; font-weight: normal;' },
|
||
_('RAG Knowledge Base + Live ISP Probe Context'),
|
||
),
|
||
],
|
||
),
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 12px; opacity: 0.8;' },
|
||
_(
|
||
'Automatically runs a diagnostic probe against the target service, queries the built-in DPI Knowledge Base, and synthesizes 3-5 custom bypass strategies tailored to your ISP.',
|
||
),
|
||
),
|
||
E('div', { style: 'display: flex; gap: 8px; align-items: center;' }, [
|
||
aiPromptInput,
|
||
aiSynthesizeBtn,
|
||
]),
|
||
aiAnalysisContainer,
|
||
],
|
||
);
|
||
|
||
tabContentAi.append(aiSynthesizerCard);
|
||
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
// TAB 5: HISTORY
|
||
// ═════════════════════════════════════════════════════════════════════════════
|
||
|
||
const renderHistoryTab = async () => {
|
||
tabContentHistory.replaceChildren();
|
||
|
||
const historyHeader = E(
|
||
'div',
|
||
{
|
||
style:
|
||
'display: flex; justify-content: space-between; align-items: center;',
|
||
},
|
||
[
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 13px; font-weight: bold;' },
|
||
'📋 ' + _('Benchmark History'),
|
||
),
|
||
E('div', { style: 'display: flex; gap: 8px;' }, [
|
||
renderButton({
|
||
text: _('🔄 Refresh'),
|
||
classNames: ['cbi-button-neutral'],
|
||
onClick: () => renderHistoryTab(),
|
||
}),
|
||
renderButton({
|
||
text: _('🗑️ Clear History'),
|
||
classNames: ['cbi-button-reset'],
|
||
onClick: async () => {
|
||
await TachyonShellMethods.clearFuzzerHistory();
|
||
showToast(_('History cleared'), 'success');
|
||
renderHistoryTab();
|
||
},
|
||
}),
|
||
]),
|
||
],
|
||
);
|
||
|
||
const historyContainer = E('div', {
|
||
style:
|
||
'border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;',
|
||
});
|
||
|
||
try {
|
||
const res = await TachyonShellMethods.getFuzzerHistory(20);
|
||
const entries = res.data?.entries || [];
|
||
|
||
if (entries.length === 0) {
|
||
historyContainer.appendChild(
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;',
|
||
},
|
||
_('No benchmark history yet. Run a benchmark to see results here.'),
|
||
),
|
||
);
|
||
} else {
|
||
const table = E(
|
||
'table',
|
||
{
|
||
class: 'table cbi-section-table',
|
||
style: 'width: 100%; margin: 0; font-size: 12px;',
|
||
},
|
||
[
|
||
E('thead', { class: 'cbi-section-table-titles' }, [
|
||
E('tr', { class: 'cbi-section-table-titles' }, [
|
||
E(
|
||
'th',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
_('Date'),
|
||
),
|
||
E(
|
||
'th',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
_('Engine'),
|
||
),
|
||
E(
|
||
'th',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
_('Target'),
|
||
),
|
||
E(
|
||
'th',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
_('DPI Type'),
|
||
),
|
||
E(
|
||
'th',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
_('Best Strategy'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px; width: 60px;',
|
||
},
|
||
_('Score'),
|
||
),
|
||
E(
|
||
'th',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px; width: 70px;',
|
||
},
|
||
_('Working'),
|
||
),
|
||
]),
|
||
]),
|
||
E(
|
||
'tbody',
|
||
{},
|
||
entries.reverse().map((entry) => {
|
||
const date = new Date(entry.timestamp * 1000);
|
||
const dateStr = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
||
const dpiType = entry.dpi_detection
|
||
? `${entry.dpi_detection.type} (${entry.dpi_detection.confidence}%)`
|
||
: '—';
|
||
const bestName = entry.best_strategy
|
||
? entry.best_strategy.name
|
||
: '—';
|
||
const bestScore = entry.best_strategy
|
||
? String(entry.best_strategy.score)
|
||
: '0';
|
||
const working = `${entry.working_count}/${entry.total_tested}`;
|
||
|
||
return E('tr', { class: 'cbi-section-table-row' }, [
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
dateStr,
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px; font-family: monospace;',
|
||
},
|
||
entry.engine,
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
entry.target,
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
dpiType,
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style:
|
||
'padding: 8px; font-size: 11px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
|
||
},
|
||
bestName,
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px; font-weight: bold;',
|
||
},
|
||
bestScore,
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px;' },
|
||
working,
|
||
),
|
||
]);
|
||
}),
|
||
),
|
||
],
|
||
);
|
||
historyContainer.appendChild(table);
|
||
}
|
||
} catch {
|
||
historyContainer.appendChild(
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;',
|
||
},
|
||
_('Failed to load history'),
|
||
),
|
||
);
|
||
}
|
||
|
||
tabContentHistory.append(historyHeader, historyContainer);
|
||
};
|
||
|
||
// ── Results Rendering ─────────────────────────────────────────────────────
|
||
const renderResults = (state: Tachyon.FuzzerState) => {
|
||
const tbody = document.getElementById('tachyon-fuzzer-results-tbody');
|
||
if (!tbody) return;
|
||
tbody.replaceChildren();
|
||
|
||
let list = state.results || [];
|
||
if (resultFilter === 'success') {
|
||
list = list.filter((r) => r.success);
|
||
} else if (resultFilter === 'fast') {
|
||
list = list.filter((r) => r.success && r.speed_kbps > 1024);
|
||
}
|
||
|
||
if (list.length === 0) {
|
||
tbody.appendChild(
|
||
E(
|
||
'tr',
|
||
{},
|
||
E(
|
||
'td',
|
||
{
|
||
colSpan: 8,
|
||
style: 'padding: 24px; text-align: center; opacity: 0.6;',
|
||
},
|
||
state.running
|
||
? _('Running benchmarks...')
|
||
: _('No strategies match the active filter.'),
|
||
),
|
||
),
|
||
);
|
||
return;
|
||
}
|
||
|
||
list.forEach((item, idx) => {
|
||
const isBest = state.best_strategy && state.best_strategy.id === item.id;
|
||
|
||
const nameChildren: (HTMLElement | string)[] = [E('span', {}, item.name)];
|
||
if (item.badge) {
|
||
nameChildren.push(
|
||
E(
|
||
'span',
|
||
{
|
||
style: `font-size: 10px; margin-left: 6px; font-weight: bold; color: ${
|
||
isBest ? '#28a745' : '#17a2b8'
|
||
};`,
|
||
},
|
||
item.badge,
|
||
),
|
||
);
|
||
}
|
||
|
||
const statusChildren: (HTMLElement | string)[] = [
|
||
E(
|
||
'span',
|
||
{
|
||
class: `badge ${item.success ? 'badge-success' : 'badge-danger'}`,
|
||
style: 'font-size: 10px; padding: 2px 6px; display: inline-block;',
|
||
},
|
||
item.success ? `HTTP ${item.http_code}` : 'DROP',
|
||
),
|
||
];
|
||
|
||
if (item.data_verified) {
|
||
statusChildren.push(
|
||
E(
|
||
'span',
|
||
{
|
||
class: 'badge badge-success',
|
||
style:
|
||
'font-size: 9px; padding: 2px 5px; margin-left: 4px; display: inline-block; font-weight: 500;',
|
||
title: _(
|
||
'Data transfer verified: streamed >= 32KB without throttling or disconnect',
|
||
),
|
||
},
|
||
_('✓ 32KB Streamed'),
|
||
),
|
||
);
|
||
} else if (item.dpi_verdict === 'throttled_16k') {
|
||
statusChildren.push(
|
||
E(
|
||
'span',
|
||
{
|
||
class: 'badge badge-danger',
|
||
style:
|
||
'font-size: 9px; padding: 2px 5px; margin-left: 4px; display: inline-block; font-weight: 500;',
|
||
title: _(
|
||
'DPI throttling: stream cut off after ~16KB payload transfer',
|
||
),
|
||
},
|
||
_('✗ Throttled at 16KB'),
|
||
),
|
||
);
|
||
} else if (item.dpi_verdict === 'server_fakes') {
|
||
statusChildren.push(
|
||
E(
|
||
'span',
|
||
{
|
||
class: 'badge badge-warning',
|
||
style:
|
||
'font-size: 9px; padding: 2px 5px; margin-left: 4px; display: inline-block; font-weight: 500;',
|
||
title: _(
|
||
'Fake packets reached remote server (HTTP 400 Bad Request)',
|
||
),
|
||
},
|
||
_('⚠️ Server received fakes'),
|
||
),
|
||
);
|
||
}
|
||
|
||
if (item.sub_probes && item.sub_probes.length > 1) {
|
||
const passedSub = item.sub_probes.filter((p) => p.success).length;
|
||
const totalSub = item.sub_probes.length;
|
||
statusChildren.push(
|
||
E(
|
||
'div',
|
||
{ style: 'font-size: 10px; opacity: 0.75; margin-top: 2px;' },
|
||
`${passedSub}/${totalSub} ` + _('endpoints OK'),
|
||
),
|
||
);
|
||
}
|
||
|
||
if (!item.success && item.error) {
|
||
statusChildren.push(
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'font-size: 10px; color: var(--error-color-medium, #dc3545); opacity: 0.85; margin-top: 2px; max-width: 220px; word-break: break-word;',
|
||
title: item.error,
|
||
},
|
||
item.error,
|
||
),
|
||
);
|
||
}
|
||
|
||
const tr = E(
|
||
'tr',
|
||
{
|
||
class: `cbi-section-table-row ${isBest ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2'}`,
|
||
style: `border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06)); ${
|
||
isBest
|
||
? 'background: var(--background-color-high, rgba(40, 167, 69, 0.08));'
|
||
: ''
|
||
}`,
|
||
},
|
||
[
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px 10px;' },
|
||
String(idx + 1),
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style:
|
||
'padding: 8px 10px; font-family: monospace; font-size: 11px;',
|
||
},
|
||
item.engine,
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px 10px;' },
|
||
[
|
||
E('div', { style: 'font-weight: 600;' }, nameChildren),
|
||
E(
|
||
'div',
|
||
{
|
||
style:
|
||
'font-size: 10px; opacity: 0.7; font-family: monospace; word-break: break-all; margin-top: 2px;',
|
||
},
|
||
item.args,
|
||
),
|
||
],
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px 10px;' },
|
||
E('div', {}, statusChildren),
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px 10px;' },
|
||
item.success ? `${item.ttfb_ms}ms` : '—',
|
||
),
|
||
E(
|
||
'td',
|
||
{ class: 'cbi-section-table-cell', style: 'padding: 8px 10px;' },
|
||
item.success
|
||
? [
|
||
E('div', {}, `${(item.speed_kbps / 1024).toFixed(1)}MB/s`),
|
||
item.data_bytes && item.data_bytes > 0
|
||
? E(
|
||
'div',
|
||
{ style: 'font-size: 10px; opacity: 0.65;' },
|
||
`${(item.data_bytes / 1024).toFixed(1)} KB`,
|
||
)
|
||
: '',
|
||
]
|
||
: '—',
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; font-weight: bold;',
|
||
},
|
||
item.success ? String(item.score) : '0',
|
||
),
|
||
E(
|
||
'td',
|
||
{
|
||
class: 'cbi-section-table-cell',
|
||
style: 'padding: 8px 10px; text-align: center;',
|
||
},
|
||
E(
|
||
'div',
|
||
{ style: 'display: flex; gap: 4px; justify-content: center;' },
|
||
[
|
||
renderButton({
|
||
text: _('Apply'),
|
||
classNames: ['cbi-button-action'],
|
||
disabled: !item.success,
|
||
onClick: () => handleApplySingle(item),
|
||
}),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
|
||
tbody.appendChild(tr);
|
||
});
|
||
};
|
||
|
||
const updateProgressUI = (state: Tachyon.FuzzerState) => {
|
||
const statusText = document.getElementById('tachyon-fuzzer-status-text');
|
||
const pctText = document.getElementById('tachyon-fuzzer-progress-pct');
|
||
const progressBar = document.getElementById('tachyon-fuzzer-progress-bar');
|
||
const currentStratEl = document.getElementById(
|
||
'tachyon-fuzzer-current-strategy',
|
||
);
|
||
const applyBestBtn = document.getElementById(
|
||
'tachyon-fuzzer-apply-best-btn',
|
||
);
|
||
|
||
if (progressContainer) {
|
||
progressContainer.style.display =
|
||
state.running || state.results?.length ? 'flex' : 'none';
|
||
}
|
||
|
||
if (pctText) pctText.innerText = `${state.progress_pct}%`;
|
||
if (progressBar) progressBar.style.width = `${state.progress_pct}%`;
|
||
|
||
const workingResults = state.results?.filter((r) => r.success) || [];
|
||
const bestStrategy = state.best_strategy || workingResults[0] || null;
|
||
|
||
if (statusText) {
|
||
if (state.running) {
|
||
statusText.innerText = `${_('Testing strategy')} ${state.current_index} / ${state.total_strategies}...`;
|
||
} else if (state.finished_at > 0 || state.results?.length) {
|
||
if (bestStrategy || workingResults.length > 0) {
|
||
if (state.error) {
|
||
statusText.innerText = `${_('🛑 Benchmark stopped')}. ${_('Found')} ${workingResults.length} ${_('working strategies')}.`;
|
||
} else {
|
||
statusText.innerText = _(
|
||
'✅ Benchmark completed! Optimal strategy identified.',
|
||
);
|
||
}
|
||
} else {
|
||
statusText.innerText = state.error
|
||
? `${_('⚠️ Benchmark stopped')}: ${state.error}`
|
||
: _(
|
||
'⚠️ Benchmark completed. No working bypass found for this target.',
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (currentStratEl) {
|
||
if (state.running && state.current_strategy) {
|
||
currentStratEl.innerText = `${_('Testing:')} [${state.current_strategy.name}] -> ${state.current_strategy.args}`;
|
||
} else {
|
||
currentStratEl.innerText = '';
|
||
}
|
||
}
|
||
|
||
if (applyBestBtn) {
|
||
(applyBestBtn as HTMLButtonElement).disabled = !bestStrategy;
|
||
}
|
||
};
|
||
|
||
// ── Polling & Execution Logic ─────────────────────────────────────────────
|
||
let lastRenderedCount = 0;
|
||
let lastRenderedFinishedAt = 0;
|
||
|
||
const pollStatus = async () => {
|
||
if (isPolling) return;
|
||
isPolling = true;
|
||
try {
|
||
const res = await TachyonShellMethods.getFuzzerStatus();
|
||
if (res.success && res.data) {
|
||
currentState = res.data;
|
||
isRunning = res.data.running;
|
||
|
||
if (isRunning) {
|
||
startBtn.innerText = _('🛑 Stop Benchmark');
|
||
(startBtn as HTMLButtonElement).disabled = false;
|
||
startPolling();
|
||
} else {
|
||
stopPolling();
|
||
startBtn.innerText = _('🚀 Start Benchmark');
|
||
(startBtn as HTMLButtonElement).disabled = false;
|
||
}
|
||
|
||
updateProgressUI(res.data);
|
||
|
||
if (res.data.dpi_detection) {
|
||
currentDpiDetection = res.data.dpi_detection;
|
||
updateDpiBanner(res.data.dpi_detection);
|
||
}
|
||
|
||
// Only re-render results when new results arrive or benchmark finishes
|
||
const resultCount = res.data.results?.length || 0;
|
||
const finishedAt = res.data.finished_at || 0;
|
||
if (
|
||
resultCount !== lastRenderedCount ||
|
||
finishedAt !== lastRenderedFinishedAt
|
||
) {
|
||
renderResults(res.data);
|
||
lastRenderedCount = resultCount;
|
||
lastRenderedFinishedAt = finishedAt;
|
||
}
|
||
|
||
if (!isRunning) {
|
||
// Auto-apply best strategy if enabled
|
||
if (autoApplyEnabled && currentState?.best_strategy) {
|
||
try {
|
||
const applyRes =
|
||
await TachyonShellMethods.autoApplyFuzzerStrategy(
|
||
selectedRuleSection || undefined,
|
||
);
|
||
if (applyRes.success) {
|
||
showToast(
|
||
`${_('✅ Auto-applied best strategy')}: ${currentState.best_strategy.name}`,
|
||
'success',
|
||
);
|
||
} else {
|
||
showToast(
|
||
`${_('Auto-apply failed')}: ${applyRes.error || _('Unknown error')}`,
|
||
'error',
|
||
);
|
||
}
|
||
} catch {
|
||
showToast(_('Auto-apply failed'), 'error');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch {
|
||
// transient error ignore
|
||
} finally {
|
||
isPolling = false;
|
||
}
|
||
};
|
||
|
||
const startPolling = () => {
|
||
stopPolling();
|
||
pollingInterval = setInterval(pollStatus, 1200);
|
||
pollStatus();
|
||
};
|
||
|
||
const stopPolling = () => {
|
||
if (pollingInterval) {
|
||
clearInterval(pollingInterval);
|
||
pollingInterval = null;
|
||
}
|
||
};
|
||
|
||
const updateDpiBanner = (detection: Tachyon.FuzzerDpiDetection | null) => {
|
||
const banner = document.getElementById('tachyon-fuzzer-dpi-banner');
|
||
if (!banner) return;
|
||
if (!detection) {
|
||
banner.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const typeMeta: Record<string, { alertClass: string; icon: string }> = {
|
||
rst: { alertClass: 'danger', icon: '🔴' },
|
||
throttle: { alertClass: 'warning', icon: '🟡' },
|
||
dns_block: { alertClass: 'info', icon: '🔵' },
|
||
ip_block: { alertClass: 'danger', icon: '🚫' },
|
||
unknown: { alertClass: 'notice', icon: '⚪' },
|
||
none: { alertClass: 'success', icon: '🟢' },
|
||
};
|
||
|
||
const typeLabels: Record<string, string> = {
|
||
rst: _('TCP Reset Injection'),
|
||
throttle: _('Throttling / Deep Inspection'),
|
||
dns_block: _('DNS Blocking'),
|
||
ip_block: _('IP Block (TCP SYN Timeout)'),
|
||
unknown: _('Unknown DPI Pattern'),
|
||
none: _('No Blocking Detected'),
|
||
};
|
||
|
||
const getLocalizedDpiDetails = (
|
||
det: Tachyon.FuzzerDpiDetection | null | undefined,
|
||
) => {
|
||
if (!det) return '';
|
||
switch (det.type) {
|
||
case 'ip_block':
|
||
return _(
|
||
'TCP connect timed out before TLS handshake — host is blocked at the IP layer. DPI bypass cannot unblock this; route via Sing-box VPN/Proxy outbound instead.',
|
||
);
|
||
case 'rst':
|
||
return _(
|
||
'TCP RST received from DPI — active TCP reset injection detected.',
|
||
);
|
||
case 'dns_block':
|
||
return _(
|
||
'DNS resolution failed — likely DNS-level blocking or hijacking.',
|
||
);
|
||
case 'throttle':
|
||
return _(
|
||
'DPI bandwidth throttling or delay detected — connection throttled during data transfer.',
|
||
);
|
||
case 'none':
|
||
return _('Target accessible — no DPI blocking detected.');
|
||
default:
|
||
return det.details || _('Analysis inconclusive.');
|
||
}
|
||
};
|
||
|
||
const meta = typeMeta[detection.type] || typeMeta.unknown;
|
||
const label = typeLabels[detection.type] || detection.type;
|
||
|
||
const ipBlockNotice =
|
||
detection.type === 'ip_block'
|
||
? `<div class="alert-message danger" style="margin-top: 8px;">
|
||
⚠️ ${_('IP-level blocking detected! The resource is blocked at the network level (no TCP SYN response). DPI bypass methods (Zapret / ByeDPI) cannot bypass this address — configure proxy/VPN routing (Sing-box) for this domain!')}
|
||
</div>`
|
||
: '';
|
||
|
||
banner.className = `alert-message ${meta.alertClass}`;
|
||
banner.style.display = 'block';
|
||
banner.innerHTML = `
|
||
<div style="display: flex; justify-content: space-between; align-items: start; flex-wrap: wrap; gap: 6px;">
|
||
<div>
|
||
<span style="font-weight: bold;">${meta.icon} ${_('DPI Detected')}: ${label}</span>
|
||
<span style="opacity: 0.7; margin-left: 8px;">${detection.confidence}% ${_('confidence')}</span>
|
||
</div>
|
||
${detection.recommended_engines.length > 0 ? `<div style="font-size: 11px; opacity: 0.8;">${_('Recommended')}: ${detection.recommended_engines.join(', ')}</div>` : ''}
|
||
</div>
|
||
<div style="margin-top: 4px; opacity: 0.9; font-size: 11px;">${getLocalizedDpiDetails(detection)}</div>
|
||
${ipBlockNotice}
|
||
`;
|
||
};
|
||
|
||
const handleToggleRun = async () => {
|
||
if (isRunning) {
|
||
(startBtn as HTMLButtonElement).disabled = true;
|
||
startBtn.innerText = _('⏳ Stopping...');
|
||
await TachyonShellMethods.stopFuzzer();
|
||
showToast(_('Benchmark stopped'), 'success');
|
||
isRunning = false;
|
||
stopPolling();
|
||
await pollStatus();
|
||
return;
|
||
}
|
||
|
||
(startBtn as HTMLButtonElement).disabled = true;
|
||
startBtn.innerText = _('🛑 Stop Benchmark');
|
||
|
||
// Immediate visual feedback
|
||
progressContainer.style.display = 'flex';
|
||
const statusTextEl = document.getElementById('tachyon-fuzzer-status-text');
|
||
const progressBarEl = document.getElementById(
|
||
'tachyon-fuzzer-progress-bar',
|
||
);
|
||
const currentStratEl = document.getElementById(
|
||
'tachyon-fuzzer-current-strategy',
|
||
);
|
||
if (statusTextEl)
|
||
statusTextEl.innerText = _('🚀 Initializing benchmark in sandbox...');
|
||
if (progressBarEl) progressBarEl.style.width = '3%';
|
||
if (currentStratEl)
|
||
currentStratEl.innerText = _(
|
||
'Preparing strategies and isolated nftables queue...',
|
||
);
|
||
|
||
// Run DPI detection before starting benchmark
|
||
try {
|
||
const dpiRes = await TachyonShellMethods.detectFuzzerDpi(
|
||
selectedTarget,
|
||
selectedTarget === 'custom' ? customUrl : undefined,
|
||
);
|
||
if (dpiRes.success && dpiRes.data) {
|
||
currentDpiDetection = dpiRes.data;
|
||
updateDpiBanner(dpiRes.data);
|
||
}
|
||
} catch {
|
||
// DPI detection is non-critical, continue with benchmark
|
||
}
|
||
|
||
const tbody = document.getElementById('tachyon-fuzzer-results-tbody');
|
||
if (tbody) {
|
||
tbody.replaceChildren(
|
||
E(
|
||
'tr',
|
||
{},
|
||
E(
|
||
'td',
|
||
{
|
||
colSpan: 8,
|
||
style: 'padding: 24px; text-align: center; opacity: 0.85;',
|
||
},
|
||
[
|
||
E('span', { style: 'margin-right: 8px;' }, '⏳'),
|
||
E(
|
||
'span',
|
||
{},
|
||
_('Testing bypass strategies in isolated sandbox...'),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
const res = await TachyonShellMethods.startFuzzer(
|
||
selectedEngine,
|
||
selectedTarget,
|
||
customUrl,
|
||
selectedRuleSection,
|
||
'',
|
||
selectedMode,
|
||
);
|
||
|
||
if (res.success) {
|
||
showToast(_('Strategy benchmark started'), 'success');
|
||
(startBtn as HTMLButtonElement).disabled = false;
|
||
isRunning = true;
|
||
lastRenderedCount = 0;
|
||
lastRenderedFinishedAt = 0;
|
||
startPolling();
|
||
} else {
|
||
const errMsg = !res.success ? res.error : _('Unknown error');
|
||
showToast(`${_('Failed to start benchmark')}: ${errMsg}`, 'error');
|
||
(startBtn as HTMLButtonElement).disabled = false;
|
||
startBtn.innerText = _('🚀 Start Benchmark');
|
||
progressContainer.style.display = 'none';
|
||
}
|
||
};
|
||
|
||
const handleAiSynthesize = async () => {
|
||
const userPrompt = (aiPromptInput as HTMLInputElement).value.trim();
|
||
(aiSynthesizeBtn as HTMLButtonElement).disabled = true;
|
||
aiSynthesizeBtn.innerText = _('🧠 Synthesizing...');
|
||
|
||
const analysisBox = document.getElementById('tachyon-fuzzer-ai-analysis');
|
||
const analysisText = document.getElementById(
|
||
'tachyon-fuzzer-ai-analysis-text',
|
||
);
|
||
if (analysisBox) analysisBox.style.display = 'none';
|
||
|
||
try {
|
||
showToast(_('Consulting DPI Knowledge Base & AI...'), 'success');
|
||
const res = await TachyonShellMethods.fuzzerAiSynthesize(
|
||
selectedEngine,
|
||
selectedTarget,
|
||
customUrl,
|
||
userPrompt,
|
||
);
|
||
|
||
if (res.success && res.data) {
|
||
showToast(
|
||
_('AI successfully synthesized custom strategies!'),
|
||
'success',
|
||
);
|
||
|
||
if (analysisBox && analysisText && res.data.analysis) {
|
||
analysisText.innerText = res.data.analysis;
|
||
analysisBox.style.display = 'block';
|
||
}
|
||
|
||
// Switch to benchmark tab and start running
|
||
activeTab = 'benchmark';
|
||
updateTabVisibility();
|
||
|
||
(startBtn as HTMLButtonElement).disabled = true;
|
||
startBtn.innerText = _('🛑 Stop Benchmark');
|
||
|
||
const startRes = await TachyonShellMethods.startFuzzer(
|
||
selectedEngine,
|
||
selectedTarget,
|
||
customUrl,
|
||
selectedRuleSection,
|
||
);
|
||
|
||
if (startRes.success) {
|
||
isRunning = true;
|
||
startPolling();
|
||
}
|
||
} else {
|
||
const errMsg = !res.success ? res.error : _('AI synthesis failed');
|
||
showToast(`${_('AI Synthesis Error')}: ${errMsg}`, 'error');
|
||
}
|
||
} catch {
|
||
showToast(_('Failed to communicate with AI provider'), 'error');
|
||
} finally {
|
||
(aiSynthesizeBtn as HTMLButtonElement).disabled = false;
|
||
aiSynthesizeBtn.innerText = _('🧠 Synthesize with AI');
|
||
}
|
||
};
|
||
|
||
const handleApplySingle = async (item: Tachyon.FuzzerStrategyResult) => {
|
||
const res = await TachyonShellMethods.applyFuzzerStrategy(
|
||
item.engine,
|
||
item.args,
|
||
selectedRuleSection,
|
||
);
|
||
if (res.success) {
|
||
if (typeof ui?.addNotification === 'function') {
|
||
ui.addNotification(
|
||
_('Tachyon'),
|
||
E(
|
||
'p',
|
||
{},
|
||
`${_('Strategy applied successfully and service reloaded!')} (${item.name} -> ${selectedRuleSection || _('Global Default')})`,
|
||
),
|
||
'info',
|
||
);
|
||
} else {
|
||
showToast(
|
||
`${_('Applied')} "${item.name}" -> ${selectedRuleSection || _('Global Default')}`,
|
||
'success',
|
||
);
|
||
}
|
||
if (typeof ui?.hideModal === 'function') ui.hideModal();
|
||
if (
|
||
typeof window !== 'undefined' &&
|
||
typeof window.location?.reload === 'function'
|
||
) {
|
||
window.location.reload();
|
||
}
|
||
} else {
|
||
showToast(_('Failed to apply strategy'), 'error');
|
||
}
|
||
};
|
||
|
||
const handleApplyBest = async () => {
|
||
const best =
|
||
currentState?.best_strategy ||
|
||
currentState?.results?.find((r) => r.success);
|
||
if (!best) {
|
||
showToast(_('No working strategy to apply'), 'error');
|
||
return;
|
||
}
|
||
await handleApplySingle(best);
|
||
};
|
||
|
||
// Initial Data Fetch: available engines & custom patterns
|
||
TachyonShellMethods.getFuzzerStrategies()
|
||
.then((res) => {
|
||
if (res.success && res.data?.available_engines) {
|
||
const av = res.data.available_engines;
|
||
const opts = (engineSelect as HTMLSelectElement).options;
|
||
for (let i = 0; i < opts.length; i++) {
|
||
const opt = opts[i];
|
||
if (opt.value === 'zapret2') {
|
||
opt.text = av.zapret2
|
||
? _('Zapret v2 (nfqws2)') + ' — ' + _('Installed')
|
||
: _('Zapret v2 (nfqws2)') + ' — ' + _('Not installed');
|
||
if (!av.zapret2) opt.disabled = true;
|
||
} else if (opt.value === 'zapret') {
|
||
opt.text = av.zapret
|
||
? _('Zapret v1 (nfqws)') + ' — ' + _('Installed')
|
||
: _('Zapret v1 (nfqws)') + ' — ' + _('Not installed');
|
||
if (!av.zapret) opt.disabled = true;
|
||
} else if (opt.value === 'byedpi') {
|
||
opt.text = av.byedpi
|
||
? _('ByeDPI (ciadpi)') + ' — ' + _('Installed')
|
||
: _('ByeDPI (ciadpi)') + ' — ' + _('Not installed');
|
||
if (!av.byedpi) opt.disabled = true;
|
||
}
|
||
}
|
||
}
|
||
})
|
||
.catch(() => {});
|
||
|
||
TachyonShellMethods.getFuzzerPatterns()
|
||
.then((res) => {
|
||
if (res.success && res.data?.patterns) {
|
||
patternsConfig = res.data.patterns;
|
||
}
|
||
})
|
||
.catch(() => {});
|
||
|
||
// Build Modal Tree
|
||
modalContainer.append(
|
||
tabBar,
|
||
tabContentBenchmark,
|
||
tabContentPatterns,
|
||
tabContentCustom,
|
||
tabContentAi,
|
||
tabContentHistory,
|
||
);
|
||
|
||
// Check initial state
|
||
pollStatus();
|
||
|
||
ui.showModal(_('⚡ Strategy Fuzzer & Auto-Tuner'), modalContainer);
|
||
}
|