from __future__ import annotations import json from collections import Counter from flask import redirect, render_template, request, url_for from cracklab.server.db.connection import connect_database from cracklab.server.repositories.clients import ClientsRepository from cracklab.server.repositories.jobs import JobsRepository from cracklab.server.repositories.methods import MethodsRepository from cracklab.server.repositories.results import ResultsRepository from cracklab.server.services.methods import MethodService, MethodServiceError from cracklab.server.web import cracklab_bp @cracklab_bp.route("/") def dashboard(): conn = connect_database() try: methods = MethodsRepository(conn).list_methods() jobs = JobsRepository(conn).list_jobs() clients = ClientsRepository(conn).list_clients() results = ResultsRepository(conn).list_results() finally: conn.close() job_statuses = Counter( row["status"] for row in jobs ) recent_jobs = jobs[:10] method_names = { row["id"]: row["name"] for row in methods } client_names = { row["id"]: row["name"] for row in clients } return render_template( "dashboard.html", active_page="dashboard", methods_count=len(methods), jobs_count=len(jobs), clients_count=len(clients), results_count=len(results), active_jobs_count=sum( job_statuses.get(status, 0) for status in ( "CREATED", "QUEUED", "RUNNING", "PARTIAL", ) ), job_statuses=job_statuses, recent_jobs=recent_jobs, method_names=method_names, client_names=client_names, ) @cracklab_bp.route("/jobs/") def jobs_page(): return _placeholder( "Jobs", "Job management will be implemented here.", "jobs", ) @cracklab_bp.route("/methods/") def methods_page(): conn = connect_database() try: service = MethodService(conn) methods = service.list_methods() method_rows = [] for method in methods: versions = service.list_method_versions(method["id"]) latest = versions[-1] if versions else None steps_count = 0 if latest is not None: try: definition = json.loads( latest["definition_json"] ) steps_count = len( definition.get("steps", []) ) except (TypeError, ValueError): steps_count = 0 method_rows.append( { "id": method["id"], "name": method["name"], "description": method["description"], "created_at": method["created_at"], "version": ( latest["version"] if latest is not None else None ), "steps_count": steps_count, } ) finally: conn.close() return render_template( "methods.html", active_page="methods", methods=method_rows, ) @cracklab_bp.route("/methods/create/", methods=["GET", "POST"]) def method_create_page(): error = None if request.method == "POST": try: name = request.form.get("name", "").strip() description = request.form.get( "description", "", ).strip() hash_mode_raw = request.form.get( "hash_mode", "22000", ).strip() if not name: raise MethodServiceError( "Введите название метода." ) if hash_mode_raw != "22000": raise MethodServiceError( "Сейчас поддерживается только hash mode 22000." ) hash_mode = 22000 steps = _build_steps_from_form() definition = { "schema_version": 1, "hash_mode": hash_mode, "steps": steps, } conn = connect_database() try: service = MethodService(conn) with conn: method_id = service.create_method( name=name, description=description or None, ) service.create_method_version( method_id=method_id, version=1, definition=definition, ) finally: conn.close() return redirect( url_for( "cracklab.methods_page", ) ) except (MethodServiceError, ValueError) as exc: error = str(exc) return render_template( "method_create.html", active_page="methods", error=error, ) def _build_steps_from_form() -> list[dict]: step_count_raw = request.form.get( "step_count", "1", ).strip() try: step_count = int(step_count_raw) except ValueError as exc: raise MethodServiceError( "Некорректное количество шагов." ) from exc if step_count < 1: raise MethodServiceError( "Метод должен содержать хотя бы один шаг." ) steps = [] for index in range(1, step_count + 1): prefix = f"step_{index}" attack_mode_raw = request.form.get( f"{prefix}_attack_mode", "", ).strip() if not attack_mode_raw: raise MethodServiceError( f"Для шага {index} не выбран attack mode." ) try: attack_mode = int(attack_mode_raw) except ValueError as exc: raise MethodServiceError( f"Некорректный attack mode в шаге {index}." ) from exc if attack_mode not in (0, 1, 3, 6, 7): raise MethodServiceError( f"Attack mode {attack_mode} " f"не поддерживается клиентом CrackLab." ) step = { "step_id": f"step-{index:03d}", "attack_mode": attack_mode, } workload_raw = request.form.get( f"{prefix}_workload_profile", "2", ).strip() if workload_raw: try: workload_profile = int(workload_raw) except ValueError as exc: raise MethodServiceError( f"Некорректный workload profile " f"в шаге {index}." ) from exc if workload_profile not in (1, 2, 3, 4): raise MethodServiceError( f"Workload profile в шаге {index} " "должен быть от 1 до 4." ) step["workload_profile"] = workload_profile if attack_mode == 0: step["dictionary"] = { "resource": _get_dictionary( prefix, "dictionary", index, ), } elif attack_mode == 1: step["dictionaries"] = [ { "resource": _get_dictionary( prefix, "dictionary_1", index, ), }, { "resource": _get_dictionary( prefix, "dictionary_2", index, ), }, ] elif attack_mode == 3: step["mask"] = _get_mask( prefix, index, ) elif attack_mode == 6: step["dictionary"] = { "resource": _get_dictionary( prefix, "dictionary", index, ), } step["mask"] = _get_mask( prefix, index, ) elif attack_mode == 7: step["mask"] = _get_mask( prefix, index, ) step["dictionary"] = { "resource": _get_dictionary( prefix, "dictionary", index, ), } steps.append(step) return steps def _get_dictionary( prefix: str, field: str, index: int, ) -> str: value = request.form.get( f"{prefix}_{field}", "", ).strip() if not value: raise MethodServiceError( f"Для шага {index} необходимо указать словарь." ) if not value.startswith("wordlist:"): value = f"wordlist:{value}" return value def _get_mask( prefix: str, index: int, ) -> str: value = request.form.get( f"{prefix}_mask", "", ).strip() if not value: raise MethodServiceError( f"Для шага {index} необходимо указать маску." ) return value @cracklab_bp.route("/clients/") def clients_page(): return _placeholder( "Clients", "Client management will be implemented here.", "clients", ) @cracklab_bp.route("/results/") def results_page(): return _placeholder( "Results", "Result history will be implemented here.", "results", ) def _placeholder( title: str, description: str, active_page: str, ): return render_template( "placeholder.html", active_page=active_page, title=title, description=description, )