diff --git a/cracklab/server/web/templates/method_create.html b/cracklab/server/web/templates/method_create.html index d4952b9..f196b58 100644 --- a/cracklab/server/web/templates/method_create.html +++ b/cracklab/server/web/templates/method_create.html @@ -1222,6 +1222,8 @@ step, mask.value, ); + + updateMaskBulkVisibility(); }, ); @@ -1340,24 +1342,291 @@ refresh(); } + function getMethodSteps() { + return Array.from( + stepsContainer.querySelectorAll( + "[data-method-step]", + ), + ); + } + + + function getBulkMaskSteps() { + return getMethodSteps().filter( + (step) => + step.dataset.bulkMaskStep === + "true", + ); + } + + + function getStepMask(step) { + const maskInput = + step.querySelector( + 'input[name$="_mask"]', + ); + + return maskInput + ? maskInput.value.trim() + : ""; + } + + + function getStepWorkloadProfile(step) { + const workload = + step.querySelector( + "[data-workload-profile]", + ); + + return workload + ? workload.value + : "2"; + } + + + function setStepWorkloadProfile( + step, + value, + ) { + const workload = + step.querySelector( + "[data-workload-profile]", + ); + + if (workload) { + workload.value = + String(value || "2"); + } + } + + + function refreshBulkMaskGroup() { + const group = + document.querySelector( + "[data-mask-bulk-group]", + ); + + const bulkSteps = + getBulkMaskSteps(); + + if (!group || bulkSteps.length === 0) { + if (group) { + group.remove(); + } + return; + } + + const firstStep = + bulkSteps[0]; + + const title = + group.querySelector( + "[data-mask-bulk-group-title]", + ); + + const count = + group.querySelector( + "[data-mask-bulk-group-count]", + ); + + const list = + group.querySelector( + "[data-mask-bulk-group-list]", + ); + + const toggle = + group.querySelector( + "[data-mask-bulk-group-toggle]", + ); + + const firstStepTitle = + firstStep.querySelector( + ".method-step-title", + ); + + const stepNumber = + firstStepTitle + ? firstStepTitle.textContent + .replace( + /^Step\s+/i, + "", + ) + .trim() + : "1"; + + title.textContent = + `Step ${stepNumber} — Mask`; + + count.textContent = + `Маски: ${bulkSteps.length}`; + + list.innerHTML = ""; + + bulkSteps.forEach( + (step) => { + const item = + document.createElement("li"); + + item.textContent = + getStepMask(step); + + list.appendChild(item); + }, + ); + + toggle.textContent = + group.classList.contains( + "is-expanded", + ) + ? "Скрыть список" + : "Показать список"; + } + + + function createBulkMaskGroup( + bulkSteps, + ) { + const existing = + document.querySelector( + "[data-mask-bulk-group]", + ); + + if (existing) { + existing.remove(); + } + + if (bulkSteps.length === 0) { + return; + } + + const group = + document.createElement("div"); + + group.className = + "method-step-bulk-group"; + + group.dataset.maskBulkGroup = + "true"; + + group.innerHTML = ` +
+ +
+ Mode 3 +
+ +
+ + + +
+ +
+
    +
    + `; + + group.querySelector( + "[data-mask-bulk-group-toggle]", + ).addEventListener( + "click", + () => { + group.classList.toggle( + "is-expanded", + ); + + refreshBulkMaskGroup(); + }, + ); + + stepsContainer.insertBefore( + group, + bulkSteps[0], + ); + + bulkSteps.forEach( + (step) => { + step.dataset.bulkMaskStep = + "true"; + + step.classList.add( + "method-step-bulk-hidden", + ); + }, + ); + + refreshBulkMaskGroup(); + } + + function updateMaskBulkVisibility() { const bulk = document.getElementById("mask-bulk"); - const hasMaskMode = - Array.from( - stepsContainer.querySelectorAll( - "[data-attack-mode]", - ), - ).some( - (modeSelect) => - modeSelect.value === "3", + const bulkSteps = + getBulkMaskSteps(); + + if (bulkSteps.length > 0) { + addMaskBulkButton.disabled = + false; + + return; + } + + const mode3Steps = + getMethodSteps().filter( + (step) => { + const modeSelect = + step.querySelector( + "[data-attack-mode]", + ); + + return ( + modeSelect && + modeSelect.value === "3" + ); + }, ); - addMaskBulkButton.disabled = - !hasMaskMode; + const manualMode3Steps = + mode3Steps.filter( + (step) => + getStepMask(step) !== "", + ); - if (!hasMaskMode) { + if (manualMode3Steps.length > 0) { + addMaskBulkButton.disabled = + true; + + bulk.classList.remove( + "is-visible", + ); + + return; + } + + addMaskBulkButton.disabled = + mode3Steps.length === 0; + + if (mode3Steps.length === 0) { bulk.classList.remove( "is-visible", ); @@ -1368,13 +1637,65 @@ function addBulkMasks( masks, ) { + const methodSteps = + getMethodSteps(); + + const sourceStep = + methodSteps.find( + (step) => { + const modeSelect = + step.querySelector( + "[data-attack-mode]", + ); + + return ( + modeSelect && + modeSelect.value === "3" + ); + }, + ); + + if (!sourceStep) { + return; + } + + const workloadProfile = + getStepWorkloadProfile( + sourceStep, + ); + + const oldBulkSteps = + getBulkMaskSteps(); + + oldBulkSteps.forEach( + (step) => { + step.remove(); + }, + ); + + const firstStep = + sourceStep; + + const bulkSteps = [ + firstStep, + ]; + + while ( + bulkSteps.length < + masks.length + ) { + bulkSteps.push( + addStep(), + ); + } + masks.forEach( - (mask) => { - const newStep = - addStep(); + (mask, index) => { + const step = + bulkSteps[index]; const modeSelect = - newStep.querySelector( + step.querySelector( "[data-attack-mode]", ); @@ -1384,25 +1705,45 @@ new Event("change"), ); - const newMask = - newStep.querySelector( + setStepWorkloadProfile( + step, + workloadProfile, + ); + + const maskInput = + step.querySelector( 'input[name$="_mask"]', ); - newMask.value = + maskInput.value = mask; - newMask.dispatchEvent( + maskInput.dispatchEvent( new Event( "input", { bubbles: true }, ), ); + + step.dataset.bulkMaskStep = + "true"; + + step.classList.add( + "method-step-bulk-hidden", + ); }, ); stepCountInput.value = stepCount; + + rebuildStepNumbers(); + + createBulkMaskGroup( + bulkSteps, + ); + + updateMaskBulkVisibility(); } @@ -1420,6 +1761,9 @@ step.className = "method-step"; + step.dataset.methodStep = + "true"; + step.dataset.step = currentStepNumber; @@ -1618,7 +1962,7 @@ () => { if ( - stepsContainer.children.length <= 1 + getMethodSteps().length <= 1 ) { return; } @@ -1626,6 +1970,7 @@ step.remove(); rebuildStepNumbers(); + updateMaskBulkVisibility(); }, ); @@ -1666,9 +2011,7 @@ function rebuildStepNumbers() { const stepElements = - Array.from( - stepsContainer.children, - ); + getMethodSteps(); stepCount = stepElements.length; @@ -1728,6 +2071,8 @@ }, ); + refreshBulkMaskGroup(); + stepCountInput.value = stepCount; } @@ -1805,18 +2150,42 @@ if (addMaskBulkButton.disabled) { return; } + const bulk = document.getElementById( "mask-bulk", ); + const textarea = + bulk.querySelector( + "[data-mask-bulk]", + ); + + const bulkSteps = + getBulkMaskSteps(); + + if (bulkSteps.length > 0) { + textarea.value = + bulkSteps + .map( + (step) => + getStepMask(step), + ) + .join("\n"); + + textarea.dispatchEvent( + new Event( + "input", + { bubbles: true }, + ), + ); + } + bulk.classList.add( "is-visible", ); - bulk.querySelector( - "[data-mask-bulk]", - ).focus(); + textarea.focus(); }, ); @@ -1838,6 +2207,31 @@ } else { addStep(); } + + const initialMode3Steps = + getMethodSteps().filter( + (step) => { + const modeSelect = + step.querySelector( + "[data-attack-mode]", + ); + + return ( + modeSelect && + modeSelect.value === "3" && + getStepMask(step) !== "" + ); + }, + ); + + if (initialMode3Steps.length > 1) { + createBulkMaskGroup( + initialMode3Steps, + ); + } + + updateMaskBulkVisibility(); + {% endblock %}