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 %}